Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
A simple way to describe a blockchain is that it’s a distributed ledger on a peer-to-peer (P2P) network where each node of that network contains a copy of the blockchain state. The state of the blockchain is updated by an application and broadcast to each node, agreeing on this new state through consensus.
Want to learn about Cosmos and the Secret Techstack via a video instead? Check this video series on the Secret Network Youtube.
Cosmos is an ecosystem of independent, interconnected and application specific blockchains commonly referred to as the internet of blockchains. By having all these application specific interconnected blockchains, the ecosystem can easily scale horizontally which avoids most of the bottlenecks and scalability issues we know today on other heterogenous blockchains.
The conceptual layers of a blockchain are:
We will cover in this article the following three layers for an ease of understanding:
Network layer: Responsible for the propagation of transactions between the nodes of the ecosystem
Consensus layer: Enables the nodes to agree on the current state of the system.
Application layer: Responsible for updating the state given a set of transactions, i.e. processing transactions
To reach adoption and grow the internet of blockchain, developers should focus on their application specific development. They should not spend time on building from scratch the consensus and networking layers. That’s what Tendermint core is taking care of by providing Networking and Consensus layers saving a lot of time and headaches for the developers.
The following figure shows the Networking and Consensus layers as “Tendermint Core” and Cosmos SDK as the “Application layer”:
On top of Tendermint Core, Cosmos introduced the Cosmos SDK, an open-source framework for building application specific blockchains with composable modules (plugins) to help developers build their blockchain faster and easier. Developers could build their application directly on Tendermint core using ABCI to interact with Tendermint but Cosmos SDK allows a faster and battle tested path to bootstrap an application.
In order to scale horizontally and fulfill the vision of an internet of interoperable blockchains, blockchains can support the Inter-Blockchain Communication Protocol (IBC), which enables value transfers, token transfers, and other communication between chains, all without the need to involve exchanges or make compromises regarding the chains' sovereignties. Following figures show blockchains interconnected with IBC (IBC Networks)\
Secret Network is built with the Cosmos SDK and the Tendermint consensus engine. Secret Network provides a platform for scalable, private permissionless smart contracts which can connect to the interchain.
Secret Network leverages novel key management techniques, encryption schemes, and Trusted Execution Environment (TEE) technology to bring encrypted input, output, and state to the blockchain.
The decentralized network of computers that host Secret Network come to a consensus (delegated Proof-of-Stake — Byzantine Fault Tolerance) without ever obtaining access to the data they process. Users can use “viewing keys” to view their sensitive data or enable third parties to do the same.
Secret brings privacy to the Interchain by leveraging CosmWasm IBC compatible smart contracts and IBC native token bridging. In this Section we dive into all of these elements of the techstack.
Steps Of A Private Transactions - overview
For a video introduction to the Secret techstack you can watch this playlist:
Blockchain technology is a complex base layer allowing open-source protocols and decentralized applications to be built on top of it. Secret is a sovereign layer 1 of the Cosmos ecosystem built with the Cosmos SDK.
This section will walk you through the Secret technology stack to get a better understanding of what’s under the hood of the privacy hub for Web3. This "Blockchain technology" section starts with a quick grasp of the different blockchain layers are and an explanation of the Cosmos stack
In this section we will explain in depth the components of the blockchain stack:
The Cosmos part of the Secret Techstack is also covered Part 1 and Part 2 of the Secret Network techstack video series.
Secret Network unlocks possibility by offering scalable permissionless smart contracts with a private by default design— bringing novel use cases to blockchain not feasible on public systems. Secret Network was the first protocol to provide private smart contracts on mainnet, live since September 2020.
Secret Network is built with the Cosmos Software Development Kit (SDK) bringing interoperable privacy to the entire Cosmos and EVM ecosystems. Secret Network uses a combination of the Intel SGX (Software Guard Extension) Trusted Execution Environment technology, several encryption schemes, and key management to bring privacy by default to blockchain users. Secret Contracts are an implementation of the Rust-based smart contract compiling toolkit CosmWasm, adding private metadata possibilities. Secret Network is powered by the Native public coin SCRT which is used for fees, Proof-of-Stake, security, and Governance. With more than 30+ Dapps on mainnet and 100+ full time builders, Secret Network aims to bring privacy to the world.
We host a weekly community developer call on Mondays at 5pm UTC on Discord. You can ask questions, get help with your project, get updates from the Secret team, and meet fellow Secret developers.
Get the latest version: https://github.com/scrtlabs/SecretNetwork
The core vision of Cosmos is to scale horizontally by having an ecosystem of interconnected application specific blockchains. Inter-Blockchain Communication Protocol (IBC) is responsible for interconnecting heterogeneous blockchains or in another way, relaying data packets between arbitrary state machines (i.e application blockchains).
It could also be defined as a generic and standard protocol implementation for transferring value between chains, not only limited to Cosmos chains as other blockchains with different consensus could support IBC to communicate with the Cosmos Ecosystem (Polkadot, Ethereum through a peg zone for example).
IBC provides a common protocol for blockchains to communicate in a standardized and trustless way.
There is a lot to talk about IBC in terms of current and future capabilities, and the technology layers, but that’s not the goal of this paragraph. We’ll go through the high level principles here.
IBC is composed of two layers:
Transport layer (IBC/TAO) - provides the infrastructure for establishing secure connections and data packets authentication between chains
Application layer (IBC/APP) - defines how data packets should be packaged and interpreted by sending and receiving chains
This is wrapped up in a “light client” and relayers are external components for passing messages through blockchains:
Relayers are off-chain actors ensuring the “physical” connection between two chains, scanning the state of the two interconnected chains, looking for an intention to send a transaction on a chain and then relaying the data and its commitment proof to the other chain.
For example in the case of fungible token transfer between two chains, the relayers are responsible for proving that your tokens are locked in Chain A and giving a representation (Voucher) on Chain B.
Relayers are using the light client of each blockchain to verify incoming messages on chain A and submit them (and the proof of commitment) on chain B. Chain A can’t send data directly to chain B and instead “commit” the hash of the data packet in its own state machine. That’s this specific state that relayers are monitoring to send this packet and its proof to the chain B.
The major difference of IBC compared to existing bridge solutions is that there are no third parties to trust, no multisig involved, for transferring value/messages between blockchains. This concept of IBC native security means that if you trust chain A and chain B then you also trust IBC-TokenA on chain B and vice versa. As long as relayers are operated by any party and channels/ports are open and authentication successful between blockchains, messages can be passed along.
An analogy of IBC could be seen as an internet application on a computer. A channel is an IP connection, with the IBC portID being an IP port and the IBC channelID being an IP address.
IBC security does not depend on third parties to verify the validity of transactions between blockchain. IBC security is mostly done by the light clients who verify proofs of commitments and the state of the two interconnected blockchain. In short terms, IBC security is based on::
Trust in the chains you connect with
Fault isolation mechanisms, to limit damage done if a chain is acting maliciously
IBC is then still Byzantine resistant thanks to the proof validation by the light client. If a relayer were to act maliciously, the packet would be rejected by the counterparty chain because the proof would be invalid (because light client and relayers are independent).
Fungible token transfer is one example but IBC allows for Non-Fungible token transfer, multi-chain smart contracts and interchain accounts (interacting on a blockchain account from another source blockchain). This is the function enabling Secret Network to be the privacy hub for the Interchain. Other Cosmos chains can store and manipulate private data (even private keys) on Secret Network from the comfort of their own chain, Privacy as a service.
Finally, below a figure explaining the travel of an IBC packet between blockchains:
An introduction to CosmWasm
CosmWasm is a modular framework for writing secure smart contracts in Rust, and using them in any blockchain built with the Cosmos SDK. It's a low-level tool developers use to implement entirely new features. The framework enables the creation of modular and reusable code for smart contracts, without being exposed to the underlying nature of the blockchain and its inner workings. These smart contracts are run securely inside a WebAssembly (WASM) virtual machine (VM). WASM acts as an intermediate language compiler for the VM.
Everything in blockchain is a smart contract. To understand what this means, it helps to know that a smart contract is simply a set of rules that describe how parties interact with each other. These rules are written as code and stored on the blockchain, where they can be executed by the network itself.
The blockchain world has seen several attempts to make smart contracts safer, but these solutions were either too complicated or simply didn't work. CosmWasm is different because it's easy to use and works well in practice.
CosmWasm wraps Rust binaries as secure smart contracts. The source code is verified by the compiler and the resulting binary contains all of its dependencies statically linked. This prevents attackers from modifying any part of the contract after it has been deployed on-chain.
You don't have to write Rust code in order to use CosmWasm. If you're a developer and want to write your own contracts using CosmWasm, you can! As long as your programming language generates a WASM binary with no external dependencies, and defines the correct entry points it will work with CosmWasm. This means you can use any programming language of your choosing. You could write your contract in Go or Python, for example, and then deploy it with CosmWasm.
💡 For more information on CosmWasm navigate to:
Secret Network uses both symmetric and asymmetric encryption protocols. Specifically, asymmetric cryptography is used for achieving consensus and sharing secrets between nodes and users, whereas symmetric cryptography is used for input/output encryption with users of Secret Contracts, as well as internal contract state encryption.
Secret Network Protocol uses the Elliptic-curve Diffie-Hellman (ECDH) key exchange mechanism between users and validators. This process involves the user, the Secret Blockchain, as well as the trusted component of the Secret Protocol. It is initiated any time a transaction is sent from the user to the Secret Contract.
The encryption and key derivation logic Secret Network uses are as follows:
HKDF-SHA256 function for deterministic key derivation;
ECDH (x25519) function for generating public / private key pairs; and
AES-128-SIV symmetric encryption scheme.
A combination of the above symmetric and asymmetric encryption methods is used to create a safe process for the bootstrapping of the decentralized network, the addition of new SGX nodes, and the encryption of input, output, and state. The management of all the private and public keys may only be shared under specific conditions, and others never leave the SGX instance to ensure private data handling.
The Secret Network uses a random number called the “consensus seed” as a parameter to derive a public and private key set used by the Network for encryption purposes. The consensus seed is unknown to any party in the ecosystem and was created at the genesis of the network by the network itself. For deterministic key generation, the network uses a known “salt” which is chosen to be the hash of the Bitcoin halving block.
As a transaction is initiated by a user of the network they derive Input Key Material (IKM) using their own private key and the network-generated public key, the network derives the same IKM using the user's public key and the network-generated private key. An encryption key for the transaction is then derived inside the TEE from the IKM, the salt, and a random number generated with the consensus seed. The encryption key is used to encrypt the input specific to this transaction. Only the protocol and the user signing the transaction have access to the encryption key, and therefore access to the input, output, and state related to this specific smart contract computation.
Smart contract blockchains are typically public by default. This means that the ledger, the transactions, and the data contained in the smart contract are accessible to anyone. However, this is not the case with Secret Network as it’s the first blockchain that offers programmable privacy through privacy-preserving smart contracts (“Secret Contracts”). “Secret Contracts” have both public and private metadata. Private data on Secret Network is encrypted at input, state, and output and therefore never accessible to any nodes, developers, users, or everyone else.
Programmable privacy is the ability to compute with private data while allowing for not only transfers (transactional privacy) but arbitrarily private complex computations. This allows developers to include sensitive data in their smart contracts without moving off-chain to centralized (and less secure) systems; allowing for truly private and scalable decentralized applications— the true vision of the decentralized web.
To achieve such programmable privacy, Secret Network uses a combination of techniques which will be explained in the further sections of this documentation.
Want to learn about Privacy for smart contracts and the Secret network techstack? Check out the following video series for an introduction:
An extensive discussion of Secret Network's cryptography
When a full node resumes network participation, it reads consensus_seed
from $HOME/.sgx_secrets/consensus_seed.sealed
, does the same key derivation steps as above in steps 2-5.
The new full node verifies the remote attestation proof of the bootstrap node from genesis.json
and creates a remote attestation proof for their own machine to show to the network that the node's Enclave is genuine.
Using HKDF-SHA256, hkdf_salt
and nonce
(a 256 bit true random) a private key is derived. From registration_privkey
calculate registration_pubkey
The node needs to send an secretcli tx register auth
transaction with the following inputs:
The remote attestation proof the node's Enclave is genuine
registration_pubkey
256 bits true random nonce
On the consensus layer, inside the enclave of every full node the auth transaction comes in.
The Network validator nodes:
Receive the secretcli tx register auth
transaction
Verify the remote attestation proof that the new node's Enclave is genuine
seed_exchange_key
This key is derived in several steps:
This IKM is never publicly available and protects the Secret network private entropy
In the second step seed_exchange_key
is derived using HKDF-SHA256 from seed_exchange_ikm
and nonce
. When sending the seed_exchange_key
to new nodes the Nonce is added as plaintext, it just serves the function of making each seed_exchange_key
unique.
consensus_seed
with the new nodeThe seed_exchange_key
generated in step 5 is used to encrypt the consensus_seed
. The AD
for this encryption algorithm is the public key of the new node: new_node_public_key
All this logic is done in side the Authorization transaction. secretcli tx register auth
outputs the encrypted_consensus_seed
The encrypted output is received by the new full node. The new node now has access to the encrypted_consensus_seed
and will have to decrypt to plaintext to receive the consensus_seed
seed_exchange_key
The AES-128-SIV encryption key seed_exchange_key
is used to decrypt consensus_seed
To derive this the reverse logic is followed highlighted in step 5.
seed_exchange_key
is derived using HKDF-SHA256 with seed_exchange_ikm
and nonce
encrypted_consensus_seed
encrypted_consensus_seed
is encrypted with AES-128-SIV, seed_exchange_key
as the encryption key and the public key of the registering node as the ad
as the decryption additional data The new node now has all of these parameters inside its Enclave, so it's able to decrypt consensus_seed
from encrypted_consensus_seed
and then seal consensus_seed
to disk at $HOME/.sgx_secrets/consensus_seed.sealed
seed_exchange_key
: An encryption key is used to send consensus_seed
to the new node
first seed_exchange_ikm
is derived using () with consensus_seed_exchange_privkey
(which exists in the enclave) and registration_pubkey
(which is a public identifier of the full node)
First the same seed_exchange_ikm
is derived using () with consensus_seed_exchange_pubkey
(public in genesis.json
) and registration_privkey
(available only inside the new node's Enclave) This is the DH-key echange in action as this is the reverse public/private input of the IKM generation in step 5.
Secret Network is set up to perform computation while having encrypted state, input and output. The state is encrypted using private keys generated during the network bootstrapping from a consensus_seed
. The bootstrap node was the first full node on the network and generated the shared secrets similar to a universal trusted zero knowledge setup.
The bootstrap node first does a remote attestation to prove they are running a genuine SGX enclave with updated software. After registering the bootstrap node handled the initialization of following variables:
Consensus_seed
a true random 256 bit seed used as entropy for generating shareable keypairs between the nodes of the network.
consensus_seed_exchange_pubkey
- consensus_seed_exchange_pubkey
an HDKF private key and a matching curve25519 public key for encryption of the random seed and sharing this with other full nodes in the network.
consensus_io_exchange_pubkey
- consensus_io_exchange_pubkey
an HDKF private key and a matching curve25519 public key for encrypting transactions IO in the network. Also referenced as the "Secret Network keypair".
consensus_state_ikm
An input keyring material (IKM) for HKDF-SHA256 is used to derive encryption keys for contract state.
consensus_callback_secret
A curve25519 private key is used to create callback signatures when contracts call other contracts
The bootstrap node proves to have a genuine enclave after which it can participate in the network. More information can be found on this page.
consensus_seed
The bootstrap node is instructed when started to generate a true random 256 bit seed inside the enclave, the consensus_seed
.
The consensus_seed
is sealed with MRSIGNER to a local file : $HOME/.sgx_secrets/consensus_seed.sealed
For the network to start decentralizing the bootstrap node needs to be able to share the consensus_seed
. The network can then use the seed to create shared secrets while in the enclave. To securely share the seed the Network uses a DH-key exchange.
Using HKDF-SHA256, hkdf_salt
and consensus_seed
a private key is derived. From consensus_seed_exchange_privkey
calculate consensus_seed_exchange_pubkey
Using HKDF-SHA256, hkdf_salt
and consensus_seed
a private key is derived. - From consensus_io_exchange_privkey
calculate consensus_io_exchange_pubkey
consensus_state_ikm
Using HKDF-SHA256, hkdf_salt
and consensus_seed
derive consensus_state_ikm
5. consensus_callback_secret
Using HKDF-SHA256, hkdf_salt
and consensus_seed
derive consensus_callback_secret
Publish to genesis.json
:
The remote attestation proof that the Enclave is genuine
consensus_seed_exchange_pubkey
consensus_io_exchange_pubkey
Secret Network leverages TEE technology to do computation with encrypted input, output, and state. The consensus and computation layer of the Secret Network is combined; every validator uses an Intel SGX CPU and processes every transaction.
Private metadata used in Secret Contracts is encrypted before sent to validators for computation. Data is only decrypted inside the TEE of any specific validator, which is inaccessible to them. Computations following the smart contract are done over the decrypted data and the output is encrypted and written to state.
The consensus encryption seed of the network is only stored inside the TEE of each validator node; no entity has access to the encryption keys.
Enclaves also go through a detailed registration and attestation process. Specifically, the attestation process which each validator running an SGX enclave must go through ensures the following assertions regarding privacy and correctness:
The application’s identity
Its intactness (that it has not been tampered with)
That it is running securely within an enclave on an Intel SGX enabled platform
For more detailed information on the Intel SGX remote attestation process you can check out this page: Remote attestation
Enclaves generate and contain their private signing/attestation keys, preventing access from any entity outside of each enclave. All data can only be signed using keys associated with specific instruction sets running in each enclave. For more details on key generation and management within enclaves, see our section about encryption.
For our purposes, the attestation key is only used once upon registration. After registration new keys are provisioned to the enclave and used to communicate with the network. This process is described in more detail below.
To do deterministic key generation inside the SGX enclave Secret Network leverages HDKF-SHA256 1 2. HDKF-SHA256 is a key derivation function for symmetric (private key) encryption. The function generates a 256-bit encryption key from a common public "salt" and a piece of Input Key Material (IKM). The salt
1 for the use in the Secret Network encryption schemes is chosen to be the Bitcoin's block halving hash hkdf_salt = 0x000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d
HDKF is commonly used to extract entropy from a larger source and deliver smaller output (eg. an encryption key) as well as expand already existing random output into a larger cryptographic-ally independent output. The deterministic keys coming from HDKF can be shared amongst network participants without revealing the underlying randomness. In the end this symmetric function is used to ensure safety for the pseudo-random consensus_seed
and secure the shared secrets of the network participants.
The output of the HDKF is a curve25519 private key, which can be used to derive a public key as well.
Elliptic-curve Diffie-Hellman ECDH (x25519) is a key derivation protocol designed to support assymetric encryption by returning a public-private key pair. ECDH allows for sharing secrets over public channels as one needs the private key to decrypt information while using the public key for sending the encrypted message. These Shared secrets can be used by both parties to then set up subsequent symmetric keys with functions like HDKF as mentioned above. ECDH delivers 256 bits Curve25519 encryption keys which have a probabilistic level of security of 2^128.
ECDH also allows for a special way of generating shared secrets which involves using the private and public key of both participants. Participant A and B can create a shared secret by doing: ecdh(Apriv, Bpub) == ecdh(Bpriv, Apub)
, this feature is called "key-exchange" and is the basis of sharing information amongst network participants on Secret Network.
For additional explanation of Diffie-Hellman, check out this video.
Advanced Encyption Standard (AES) is an encryption algorithm slightly varying from the block cipher "Rijndael" set to a fixed 128 bits size block. The algorithm generates 256 bit encryption keys which offer very high security guarantees.
The AES-SIV encryption scheme is a perfect addition to the ECDH keypairs used in SGX enclaves. The combination allows for sharing encrypted data amongst nodes and protecting the private entropy of the protocol. AES-128-SIV was chosen to prevent IV misuse by client libraries. The algorithm does not pad ciphertext which leaks information about the plaintext, in particular its size.
When a contract is executed on chain the state of the contracts needs to be encrypted so that observers can not see the computation that is initialized. The contract should be able to call certain functions inside the enclave and store the contract state on-chain.
A contract can call 3 different functions: write_db(field_name, value)
, read_db(field_name)
, and remove_db(field_name)
. It is important that the field_name
remains constant between contract calls.
We will go over the different steps associated with the encryption of the contract state.
contract_key
The contract_key
is the encryption key for the contract state and is a combination of two values: signer_id || authenticated_contract_key
. Every contract has its own unforgeable encryption key. The concatenation of the values is what makes every unique and this is important for several reasons
Make sure the state of two contracts with the same code is different
Make sure a malicious node runner won't be able to locally encrypt transactions with it's own encryption key, and then decrypt the resulting state with the fake key
so to reiterate, every contract on Secret Network has its own unique and unforgeable encryption key contract_key
This process of creating contract_key
is started when the Secret contract is deployed on-chain. First authentication_key
is generated using HDKF-SHA256 inside the enclave from the following values:
consensus_state_ikm
HDK-salt
signer_id
From the authentication_key
create authenticated_contract_key
by calling the hmac-SHA256 hash function with the contract code_hash
as hashing data.
This step makes sure the key is unique for every contracts with different code.
Lastly concat the signer_id
and authenticated_contract_key
to create contract_key
. This step makes it so the key is unforgeable as the key can only be recreated with the current signer_id
contract_key
with enclaveEvery time a contract execution is called, contract_key
should be sent to the enclave. In the enclave, the following verification needs to happen to proof a genuine contract_key
write_db(field_name, value)
read_db(field_name)
remove_db(field_name)
Very similar to read_db
.
Transaction encryption unlike contract state encryption has two parties who need data access. The scheme therefore makes use of the DH-key exchange as described in the previous section to generate a shared encryption key. This symmetric tx_encryption_key
is unique for every transaction and can be used by both the network and the user to verify the completed transactions.
Using the Eliptic-Curve Diffie Hellman key exchange (ECDH) the user generates a shared secret from consensus_io_exchange_pubkey
and tx_sender_wallet_privkey
.
tx_encryption_key
- user sideThe user then generates a shared tx_encryption_key
using HKDF-SHA256 and the tx_encryption_ikm
generated in step 1. The pseudo-random HDKF is used to ensure deterministic consensus across all nodes.
The random component comes from a 256-bit nonce so that each transaction has its own encryption key, An AES-256-GCM encryption key is never used twice.
After initiating a transaction the user encrypts the input data with the shared transaction encryption key, using an AES-256-GCM authenticated encryption scheme.
The input (
msg
) to the contract is always prepended with the sha256 hash of the contract's code. This is meant to prevent replaying an encrypted input of a legitimate contract to a malicious contract, and asking the malicious contract to decrypt the input.
In this attack example the output will still be encrypted with a tx_encryption_key
that only the original sender knows, but the malicious contract can be written to save the decrypted input to its state, and then via a getter with no access control retrieve the encrypted input.
tx_ecryption_key
- network sideThe enclave uses ECDH to derive the same tx_encryption_ikm
from the tx_sender_wallet_pubkey
and the consensus_io_exchange_privkey
. The network then derives the tx_encryption_key
from the publicly signed nonce
and this shared secret using HDKF.
Within the trusted component the transaction input is decrypted to plaintext.
The output must be a valid JSON object, as it is passed to multiple mechanisms for final processing:
Logs are treated as Tendermint events
Messages can be callbacks to another contract call or contract init
Messages can also instruct sending funds from the contract's wallet
A data section which is free-form bytes to be interpreted by the client (or dApp)
An error section
Here is an example output for an execution:
on a Contract
message, the msg
value should be the same msg
as in our tx_input
, so we need to prepend the nonce
and tx_sender_wallet_pubkey
just like we did on the tx sender above
On a Contract
message, we also send a callback_signature
, so we can verify the parameters sent to the enclave (read more here: ......)
For the rest of the encrypted outputs we only need to send the ciphertext, as the tx sender can get consensus_io_exchange_pubkey
from genesis.json
and nonce
from the tx_input
that is attached to the tx_output
with this info only they can decrypt the transaction details.
Here is an example output with an error:
An example output for a query:
The output of the computation is encrypted using the tx_encryption_key
The transaction output is written to the chain and only the wallet with the right tx_sender_wallet_privkey
can derive tx_encryption_key
. To everyone else but the tx signer the transaction data will be private.
Every encrypted value can be decrypted by the user following:
For output["ok"]["messages"][i]["type"] == "Contract"
, output["ok"]["messages"][i]["msg"]
will be decrypted in by the consensus layer when it handles the contract callback
Intel SGX is one of the most used and widely available implementations of Trusted Execution Environments (TEEs). Secret Network has selected this technology for the initial version of the Secret Network for two main reasons: Usability & Security.
SGX is more performant and more flexible than other solutions for privacy-preserving computation. The Secret Network is building a platform for decentralized, general-purpose private computation. This requires a privacy solution that can enable a wide range of use cases. It also requires computations to be on par, performance-wise, with non-privacy preserving computation, so that speed does not limit application usability.
SGX is one of the most widely adopted technologies for TEEs, it is also battle-hardened. Attacks are often theoretical, executed in laboratory settings, and are rapidly addressed by Intel. Many high-value targets exist that have not been compromised. No privacy solution is 100% secure, but we believe the security guarantees provided by Intel SGX are adequate for a wide range of use cases.
Secret Network only allows for Intel SGX chips, AMD-SEV or other TEE technologies are not usable for running nodes on the network.
SGX comes in 2 forms; SGX-ME and SGX-SPS. SGX-ME (management engine) uses small extra chips to manage functions related to the enclave such as memory and energy management. SGX-SPS (Server Platform Services) allows the bypassing of the ME chip. To further reduce the number of possible attack vectors on the network, Secret Network has opted to only use SGX-SPS. Hence, all attack vectors of the ME chip do not apply to Secret Network.
Furthermore, each full node on Secret Network creates an attestation report that proves that their CPU is using the latest firmware/microcode (processor firmware) upgrades before it registers. The entire network verifies the attestation report of the new node on-chain, to ensure that node operators cannot decrypt anything. Once the new node gets the shared key of the consensus they become part of the consensus and are able to process computations and transactions in parallel to the network.
Secret Network recently upgraded to version 1.7.1, which rotated the network consensus seed during the upgrade.
A consensus seed is a true random 256 bit seed that is used as entropy for generating shareable keypairs between the nodes of the network.
Previously, the consensus seed remained unchanged ever since the network’s inception–the network state was encrypted using private keys generated during the network bootstrapping from a consensus seed, similar to a universal trusted zero knowledge setup. However, if anyone were to gain access to this seed they would have the master key to decrypt the state of the entire network. Thus, Secret Network has introduced consensus seed rotation in order to increase network security.
In order to protect against potential future breaches, Secret Network developed a two-part protocol for changing the consensus seed:
Rotate the current seed (The "Genesis" seed) and change the encryption scheme
Implement contract state migration and allow seed rotation on upgrade (currently in development)
It is important to note that the upgrade to consensus seed rotation does not change the state (and the consensus seed) of the network prior to the upgrade. This means that the new encryption scheme must be able to distinguish between values that were encrypted with the genesis seed and those that will be encrypted with future seeds. To this end, the following features were implemented:
A way to distinguish between values that were encrypted with the genesis seed and those that will be encrypted with future seeds
The ability to iterate over all of the keys for a specific contract using CosmWasm iterators (currently in development)
The ability to decrypt state keys, rotate the seed, and decrypt and re-encrypt all keys & values in the state
In order to rotate the Genesis seed, a new seed must be created that will be shared with all current nodes as well as new nodes. To this end, Secret Labs updated all of the existing nodes with the new seed and every new node that joins the network will contain 2 consensus seeds (The genesis and the current) and will use them both based on the encrypted value.
The new seed can be received via three methods:
On upgrade: When upgrading to 1.7.1, the node will identify that the current seed is missing and will communicate with Secret Labs’ seed service in order to obtain the seed
On Registration: On registration, both the current and the genesis seed will be passed to the newly registered node
On Bootstrap: Boostrap will access the seed service unless the "use_seed_service_on_bootstrap" feature is off (Which is the default state in localsecret). If so, it will generate one instead.
In the previous encryption scheme, the key and the value were stored as follows:
In the new encryption scheme, the plaintext key is no longer necessary in order to decrypt the value. The encrypted value also looks a bit different in the new scheme:
This new encryption scheme ensures that:
the encrypted_state_key encrypts differently between different keys
The salt will verify that on different instances, the same value is encrypted differently for the same key. The salt is the current block's timestamp and the msg id, which is a counter of the messages and allows for different values between different messages in the same block.
In order to authenticate a node, the node first sends an attestation report to the designated /authenticate endpoint as the request body. The server then responds with a challenge, which is a randomly generated 4-byte value that is linked to the public key of the node. The node then creates a new attestation report that incorporates the challenge into its quote, which proves that the node can generate new attestation reports certified by Intel while also rendering old certificates invalid. Subsequently, the node transmits this new attestation report to the /seed/[id] endpoint, where the ID represents the desired seed. Upon receiving the attestation report, the server verifies it and sends the seed to the node.
The port of the service is 4487 and there are 2 DNS names that are used.
On MainNet - sss.scrtlabs.com
On Pulsar - sssd.scrtlabs.com
Secret Network has implemented consensus seed rotation in the upgrade to version 1.7.1. Previously, the consensus seed remained unchanged since the network's inception, but this posed a security risk as anyone with access to the seed would have the master key to decrypt the entire network's state. The new consensus seed rotation includes a two-part protocol to change the genesis seed and encryption scheme, and implement contract state migration to allow seed rotation on upgrade. The upgrade does not change the network state prior to the upgrade, so the new encryption scheme distinguishes between values encrypted with the genesis seed and those encrypted with future seeds. Secret Labs updated all existing nodes with the new seed and new nodes joining the network will have both the genesis and current seeds, using them based on the encrypted value. These updates increase the security of Secret Network by protecting against potential future breaches.
The main difference when it comes to Cosmos compared to virtual-machine blockchain, is the application-specific blockchain concept. Developers can build from scratch their application specific blockchain on top of Tendermint through ABCI protocol. Cosmos SDK is the framework offering a bank of independent modules for implementing the application state machine, ABCI, service routers to route messages between modules, and a flavor of features like governance, staking, slashing, etc …
Below a figure showing the architecture differences between a VM blockchain and Cosmos:
A virtual-machine interprets Smart Contracts to change the state of the underlying blockchain state machine. It’s developer friendly and easy to use to deploy applications but comes with certain limitations:
Specific programming language accepted by the VM
VM limited set of functions and lack of flexibility
Smart Contracts are all run by the same virtual machine restraining performance as all application compete for block resources
Limited sovereignty, meaning that the application is dependent of the underlying blockchain governance decision
In Cosmos, developers have all the keys to develop a sovereign, secure, flexible and tailor made application specific blockchain.
Cosmos SDK allows developers to tweak, if needed, the framework or the consensus engine or any modules to match their application/network requirements.
As the application is not competing with others for blockspace or is limited by the VM computation, the performance is enhanced and only limited by the state-machine itself.
In terms of security, developers are not constrained by the VM cryptographic functions or any VM exploitable mechanisms and can rely on their own cryptography or audited libraries.
In conclusion Cosmos SDK is giving an easy and fast way to bootstrap an application specific blockchain relying on an efficient and proven technology without tradeoffs on security and sovereignty and with access to an extensive modules library.
💡 For more information about the Cosmos SDK: High-level Overview | Cosmos SDK & Main Components of the Cosmos SDK
Trusted Execution Environments are essentially stateless. To preserve information that’s stored in an enclave, it must be explicitly sent outside the enclave to untrusted memory. SGX provides a capability called which encrypts enclave data in the enclave using an encryption key derived from the CPU. This encrypted data block can only be decrypted, or unsealed, on the same system. This SGX-specific method for storing data is not used to store computation input/output data in the Secret Network. It is used to store the enclave’s signing key.
We seal the signing key because this key is created during the remote attestation process. We do not want the enclave to be required to perform between each computation. If the enclave fails for some reason, and the key is lost, the worker would be obligated to go through the remote attestation process again. The only way to store persistent data from the enclave is through sealing.
The "Getting Started" section is to help new community members start developing on Secret Network using standard tools for Secret Contract development. The goal is to provide an easy guide to follow with step-by-step instructions. We will also review some of the unique characteristics of contracts on Secret Network, though we will keep things at a very high level.
Topics covered:
Setting up a development Environment
Compiling and Deploying a Secret Contract
Interacting with Contracts on the Secret Network
Breakdown of a Secret Contract (optional)
Most of the topics covered focus directly on Secret Network but are also relevant to other cosmos-based blockchains.
If at any point you have any trouble understanding or following along, to get help from the community!
If you have suggestions, improvements, or corrections, !
Secret Contracts are enabled by the “compute” module of the Cosmos SDK, and execute over plaintext while still allowing for encrypted inputs, outputs, and states because of trusted and verifiable computations. Consensus with an encrypted state is reached by using shared secrets amongst the validator nodes of the network.
In this part of the documentation, we highlight the flow of data for interactions on the secret network and dive into the design of Secret Contracts and their differentiations from standard CosmWasm contracts.
A Secret Contract’s code is always deployed publicly on-chain, so users and developers know exactly what code will be executed on submitted data. However, the data that is submitted is encrypted, it cannot be read by a developer, anyone observing the chain, or anyone running a node. If the behavior of the code is trusted (which is possible because it is recorded on chain), a user of Secret Contracts obtains strong privacy guarantees.
The encrypted data can only be accessed from within the “trusted execution environment”, or enclave, that the compute module requires each validator to run. The computation of the Secret Contract is then performed, within this trusted enclave, where the data is decrypted. When the computation is completed, the output is encrypted and recorded on-chain.
Want to learn more about the encryption specification of the contract state? - Read the technical specification on Contract state encryption
Secret contracts are an altered version of the CosmWasm Rust based smart contract framework and share many resemblance. The contracts written in Rust compile to a Wasm binary that is than run by the Wasmd module of the cosmos SDK. The version of Secret is altered in such a way that all executions are done inside the secure enclave requiring additional data verification and more. This also means queries of the contract state or contract execution are permissioned to only the signer of the transaction themselves. Contract state queries requiring opening up the VM in the enclave and are therefore more intensive to run than generic plaintext cosmos-sdk queries.
Remote attestation, an advanced feature of Intel SGX, is the process of proving an enclave is established in a secure hardware environment. A remote party should be able to verify that the right application is running inside an enclave on an Intel SGX enabled platform.
Remote attestation provides verification for three things:
The application’s identity
Its intactness (that it has not been tampered with)
That it's running securely within an enclave on an Intel SGX enabled platform
Attestation is necessary in order to make remote access secure because an enclave’s contents may have to be accessed remotely, not from the same platform []
The attestation process consists of seven stages, encompassing several actors, namely the service provider (referred to as a challenger) on one platform; and the application, the application’s enclave, the Intel-provided Quoting Enclave (QE), and Provisioning Enclave (PvE) on another platform. A separate entity in the attestation process is Intel Attestation Service (IAS), which carries out the verification of the enclave [][][].
In short, the seven stages of remote attestation comprise of making a remote attestation request (stage 1), performing a local attestation (stages 2-3), converting the local attestation to a remote attestation (stages 4-5), returning the remote attestation to the challenger (stage 6), and verifying the remote attestation (stage 7) [][].
Intel Remote Attestation also includes the establishment of a secure communication session between the service provider and the application. This is analogous to how the familiar TLS handshake includes both authentication and session establishment.
Crates.io serves as a centralized repository for Rust packages, also known as "crates." When building applications or libraries with Rust, developers often need to use external libraries for functionality not provided by the standard library, many of which are included on Crates.io. Below is an example of how to import Secret Labs' crates, such as secret-cosmwasm-std
, a fork of the original cosmwasm-storage
repository adapted for use in SecretNetwork's Secret Contracts.
You can think of "crates" as individual packages or modules in Rust, while "dependencies" refer to external crates that your project relies on for additional functionality.
When developing a Rust project, you will often have dependencies on other crates. These dependencies can come from crates.io (the default Rust package registry), a Git repository (such as one hosted by Secret Labs!), or a local path on your system. The Rust package manager, Cargo, is responsible for managing these dependencies, ensuring that the correct versions are fetched and built, and handling transitive dependencies (i.e., the dependencies of your dependencies).
Crates: A crate is a package or a module in Rust containing code and resources, like libraries or applications. Crates are the building blocks of Rust projects and are intended to be easily shared and reused. A crate can be a binary (application) or a library. Each crate has a unique name and a Cargo.toml
file containing metadata, such as the crate's name, version, authors, and dependencies.
Dependencies: Dependencies refer to external crates that your Rust project or library relies on for additional functionality. These external crates are not part of your project's codebase but are required for your project to build and function correctly. Dependencies are declared in the Cargo.toml
file of your project under the [dependencies]
section.
Environment configuration instructions to get started developing on Secret Network.
Secret Contracts are written using the CosmWasm framework. CosmWasm contracts are written in Rust, which is later compiled to WebAssembly (or WASM for short). To write our first Secret Contract, we need to set up a development environment with all of the tools required so that you can upload, instantiate, and execute your smart contracts.
For a step-by-step Secret Network environment configuration video tutorial, follow along here 🎥. Otherwise, continue reading!
To follow along with the guide, we will be using git
, make
, rust
, and docker
.
Install git
:
Download the latest Git for Mac installer.
Follow the prompts to install Git.
Open a terminal and verify the installation was successful by typing git --version
Install make
:
Install git
and perl
(for Windows):
Go to https://git-scm.com/download/win and the download will start automatically. Note that this is a project called Git for Windows, which is separate from Git itself; for more information on it, go to https://gitforwindows.org.
Go to https://strawberryperl.com and download the recommended version for your system. StrawberryPerl is an open-source Perl environment for Windows; for more information, visit https://perl.org. Perl is used to build other dependencies that will be installed later.
Note: support for make
on Windows is limited, so we'll provide separate commands for Windows where necessary
Download and run rustup-init.exe
.
Download and run the Rust .msi installer
Having Trouble? You might need to restart your terminal, or run a command like:
source "$HOME/.cargo/env"
After installing Rust to configure the current shell
Cargo generate is the tool you'll use to create a smart contract project. Learn more about cargo-generate
here.
Docker is an open platform for developing, shipping, and running applications.
SecretCLI is a command-line tool that lets us interact with the Secret Network blockchain. It is used to send and query data as well as manage user keys and wallets.
Run the following commands in Powershell to download the latest version of SecretCLI and add it to your profile's PATH:
Afterwards, restart the terminal and test the installation with the following command:
For a more detailed and in-depth guide on SecretCLI installation and usage, check out the documentation here.
Now it's time to learn how to compile and deploy your first smart contract 🎉
Additional development concepts to improve your experience building on Secret Network.
A description of CosmWasm and the framework around it as implemented on Secret Network
This overview examines the core components of Secret Network smart contracts
Get started developing on Secret Network using the public testnet and secret.js
In the previous section, we learned how to upload, execute, and query a Secret Network smart contract using SecretCLI and Secret testnet. Now we are going to repeat this process, but we will be uploading, executing, and querying our smart contract on a public testnet using Secret.js.
Secret.js is a JavaScript SDK for writing applications that interact with the Secret Network blockchain.
Key features include:
Written in TypeScript and provided with type definitions.
Provides simple abstractions over core data structures.
Supports every possible message and transaction type.
Exposes every possible query type.
Handles input/output encryption/decryption for Secret Contracts.
Works in Node.js, modern web browsers and React Native.
By the end of this tutorial, you will learn how to:
Add the Secret Testnet to your keplr wallet (and fund your wallet with testnet tokens)
Optimize and compile your Secret Network smart contract
Upload and Instantiate your contract using Secret.js
Execute and Query your contract using Secret.js
Let's get started!
To follow along with the guide, we will be using npm,
git,
make,
rust,
and docker
. Follow the Setting Up Your Environment guide here if you need any assistance.
Additionally, you will need to have the Secret Testnet configured with your keplr wallet and also fund it with testnet tokens.
We will be working with a basic counter contract, which allows users to increment a counter variable by 1 and also reset the counter. If you've never worked with smart contracts written in Rust before that is perfectly fine.
The first thing you need to do is clone the counter contract from the Secret Network github repo. Secret Network developed this counter contract template so that developers have a simple structure to work with when developing new smart contracts, but we're going to use the contract exactly as it is for learning purposes.
Go to the folder in which you want to save your counter smart contract and run:
When you run the above code, it will name your contract folder directory "my-counter-contract". But you can change the name by altering the text that follows the --name
flag.
Start by opening the my-counter-contract
project folder in your text editor. If you navigate to my-counter-contract/src
you will seecontract.rs, msg.rs, lib.rs, and state.rs
—these are the files that make up our counter smart contract. If you've never worked with a Rust smart contract before, perhaps take some time to familiarize yourself with the code, although in this tutorial we will not be going into detail discussing the contract logic.
In your root project folder, ie my-counter-contract
, create a new folder. For this tutorial I am choosing to call the folder node
.
In your my-counter-contract/node
folder, create a new javascript file––I chose to name mine upload.js
.
Run npm init -y
to create a package.json file.
Add "type" : "module"
to your package.json file.
Install secret.js and dotenv: npm i secretjs@v1.15.0-beta.1 dotenv
Create a .env
file in your node
folder, and add the variable MNEMONIC
along with your wallet address seed phrase, like so:
Never use a wallet with actual funds when working with the testnet. If your seed phrase were pushed to github you could lose all of your funds. Create a new wallet that you use solely for working with the testnet.
Congrats! 🎉 You now have your environment configured to develop with secret.js.
Since we are not making any changes to the contract code, we are going to compile it exactly as it is. To compile the code, run make
build-mainnet-reproducible
in your terminal. This will take our Rust code and build a Web Assembly file that we can deploy to Secret Network. Basically, it just takes the smart contract that we've written and translates the code into a language that the blockchain can understand.
Run make build
from the terminal, or just GUI it up -
This will create a contract.wasm.gz
file in the root directory.
Now that we have a working contract and an optimized wasm file, we can upload it to the blockchain and see it in action. This is called storing the contract code. We are using a public testnet environment, but the same commands apply no matter which network you want to use - local, public testnet, or mainnet.
Start by configuring your upload.js
file like so:
You now have secret.js imported, a wallet
variable that points to your wallet address, and a contract_wasm
variable that points to the smart contract wasm file that we are going to upload to the testnet. The next step is to configure your Secret Network Client, which is used to broadcast transactions, send queries and receive chain information.
Note the chainId and the url that we are using. This chainId and url are for the Secret Network testnet. If you want to upload to LocalSecret or Mainnet instead, all you would need to do is swap out the chainId and url. A list of alternate API endpoints can be found here.
Now console.log(secretjs)
and run node upload.js
in the terminal of your my-counter-contract/node
folder to see that you have successfully connected to the Secret Network Client:
To upload a compiled contract to Secret Network, you can use the following code:
Run node upload.js
in your terminal to call the upload_contract()
function. Upon successful upload, a codeId and a contractCodeHash will be logged in your terminal:
Be sure to save the codeId
and contractCodeHash
as variables so you can access them in additional function calls.
In the previous step, we stored the contract code on the blockchain. To actually use it, we need to instantiate a new instance of it. Comment out upload_contract()
and then add instantiate_contract()
.
Note that there is an initMsg
which contains count:0
. You can make the starting count whatever you'd like (as well as the contract label
).
Run node upload.js
in your terminal to call the instantiate_contract()
function. Upon successful instantiation, a contractAddress will be logged in your terminal:
Be sure to save the contractAddress as a variable so you can access it in additional function calls.
Congrats 🎉! You just finished uploading and instantiating your first contract on a public Secret Network testnet! Now it's time to see it in action!
The way you interact with contracts on a blockchain is by sending contract messages. A message contains the JSON description of a specific action that should be taken on behalf of the sender, and in most Rust smart contracts they are defined in the msg.rs
file.
In our msg.rs
file, there are two enums: ExecuteMsg
, and QueryMsg
. They are enums because each variant of them represents a different message which can be sent. For example, the ExecuteMsg::Increment
corresponds to the try_increment
message in our contract.rs
file.
In the previous section, we compiled, uploaded and instantiated our counter smart contract. Now we are going to query the contract and also execute messages to update the contract state. Let's start by querying the counter contract we instantiated.
A Query Message is used to request information from a contract; unlike execute messages
, query messages do not change contract state, and are used just like database queries. You can think of queries as questions you can ask a smart contract.
Let's query our counter smart contract to return the current count. It should be 0, because that was the count we instantiated in the previous section. We query the count by calling the Query Message get_count {}
, which is defined in our msg.rs file. Comment out instantiate_contract()
and then add try_query_count()
.
The query returns:
Great! Now that we have queried the contract's starting count, let's execute an increment{}
message to modify the contract state.
An Execute Message is used for handling messages which modify contract state. They are used to perform contract actions.
Did you know? Messages aren't free! Each transaction costs a small fee, which represents how many resources were required to complete it. This cost is measured in gas units.
The counter contract consists of two execute messages: increment{}
, which increments the count by 1, and reset{}
, which resets the count to any i32
you want. The current count is 0, let's call the Execute Message increment{}
to increase the contract count by 1.
Nice work! Now we can query the contract once again to see if the contract state was successfully incremented by 1. The query returns:
Way to go! You have now successfully interacted with a Secret Network smart contract on the public testnet!
Congratulations! You completed the tutorial and successfully compiled, uploaded, deployed and executed a Secret Contract! The contract is the business logic that powers a blockchain application, but a full application contains other components as well. If you want to learn more about Secret Contracts, or explore what you just did more in depth, feel free to explore these awesome resources:
Millionaire's problem breakdown - explains how a Secret Contract works
CosmWasm Documentation - everything you want to know about CosmWasm
Secret.JS - Building a web UI for a Secret Contract
Learn how to use SecretCLI to handle messages to query and modify contract state.
The way you interact with contracts on a blockchain is by sending contract messages. A message contains the JSON description of a specific action that should be taken on behalf of the sender, and in most Rust smart contracts they are defined in the msg.rs
file.
In our msg.rs
file, there are two enums: ExecuteMsg
, and QueryMsg
. They are enums because each variant of them represents a different message which can be sent. For example, the ExecuteMsg::Increment
corresponds to the try_increment
message in our contract.rs
file.
In the previous section, we compiled, uploaded and instantiated our counter smart contract. Now we are going to query the contract and also execute messages to update the contract state. Let's start by querying the counter contract we instantiated.
A Query Message is used to request information from a contract; unlike execute messages
, query messages do not change contract state, and are used just like database queries. You can think of queries as questions you can ask a smart contract.
Let's query our counter smart contract to return the current count. It should be 1, because that was the count we instantiated in the previous section. We query the count by calling the Query Message get_count {}
, which is defined in our msg.rs file.
The query returns:
Great! Now that we have queried the contract's starting count, let's execute an increment{}
message to modify the contract state.
An Execute Message is used for handling messages which modify contract state. They are used to perform actual actions.
Did you know? Messages aren't free! Each transaction costs a small fee, which represents how many resources were required to complete it. This cost is measured in gas units.
The counter contract consists of two execute messages: increment{}
, which increments the count by 1, and reset{}
, which resets the count to any i32
you want. The current count is 1, let's call the Execute Message increment{}
to increase the contract count by 1:
Pro Tip: SecretCLI automatically encrypts transactions. Only the transaction sender can see the data being sent (and the result). You can think of this as how HTTPS protects your data in transit when accessing a web page.
SecretCLI will ask you to confirm the transaction before signing and broadcasting. Upon successful confirmation of the transaction, the terminal will return a transaction hash
representing your transaction:
Nice work! Now we can query the contract once again to see if the contract state was successfully incremented by 1:
The query returns:
Now, we will call one final execute message, reset{}
. This will reset the count to an i32
that we specify. I am going to reset the count to 0 by running the following code in SecretCLI:
Make sure your JSON message is formatted properly!
'{"reset": {"count": 0}}`
The query returns:
Way to go! You have now successfully interacted with a Secret Network smart contract using SecretCLI.
Congratulations! You completed the tutorial and successfully compiled, uploaded, deployed and executed a Secret Contract! The contract is the business logic that powers a blockchain application, but a full application contains other components as well. If you want to learn more about Secret Contracts, or explore what you just did more in depth, feel free to explore these awesome resources:
Millionaire's problem breakdown - explains how a Secret Contract works
CosmWasm Documentation - everything you want to know about CosmWasm
Secret.JS - Building a web UI for a Secret Contract
In this example, we will compile, upload, and instantiate our first smart contract using SecretCLI and Secret Testnet.
Now that you've set up your development environment, we are going to learn how to compile, upload, and instantiate a smart contract using SecretCLI in your testnet environment.
For a step-by-step Secret Network SecretCLI video tutorial, follow along here 🎥. Otherwise, continue reading!
We will be working with a basic counter contract, which allows users to increment a counter variable by 1 and also reset the counter. If you've never worked with smart contracts written in Rust before that is perfectly fine. By the end of this tutorial you will know how to upload and instantiate a Secret Network smart contract in your terminal using SecretCLI.
The first thing we need to do is clone the counter contract from the Secret Network github repo. Secret Network developed this counter contract template so that developers have a simple structure to work with when developing new smart contracts, but we're going to use the contract exactly as it is for learning purposes.
Go to the folder in which you want to save your counter smart contract and run:
When you run the above code, it will name your contract folder directory "my-counter-contract". But you can change the name by altering the text that follows the --name
flag.
Start by opening the my-counter-contract
project folder in your text editor. If you navigate to my-counter-contract/src
you will seecontract.rs, msg.rs, lib.rs, and state.rs
—these are the files that make up our counter smart contract. If you've never worked with a Rust smart contract before, perhaps take some time to familiarize yourself with the code, although in this tutorial we will not be going into detail discussing the contract logic.
Since we are not making any changes to the contract code, we are going to compile it exactly as it is. To compile the code, run make build-mainnet-reproducible
in your terminal. This will take our Rust code and build a Web Assembly file that we can deploy to Secret Network. Basically, it just takes the smart contract that we've written and translates the code into a language that the blockchain can understand.
Run make build
from the terminal, or just GUI it up -
This will create a contract.wasm.gz
file in the root directory.
Now that we have a working contract and an optimized wasm file, we can upload it to the blockchain and see it in action. This is called storing the contract code. We are using a testnet environment, but the same commands apply no matter which network you want to use - local, public testnet, or mainnet.
In order to store the contract code, we first must create a wallet address in order to pay for transactions such as uploading and executing the contract.
To interact with the blockchain, we first need to initialize a wallet. From the terminal run:
secretcli keys add myWallet
You can name your wallet whatever you'd like, for this tutorial I chose to name my wallet "myWallet"
You should now have access to a wallet with a unique name, address, and mnemonic, which you can use to upload the contract to the blockchain:
The wallet currently has zero funds, which you query by running this secretcli command (be sure to use your wallet address in place of mine)
To fund the wallet so that it can execute transactions, you can get testnet tokens from the faucet here.
Finally, we can upload our contract:
--from <name>
refers to which account (or wallet) is sending the transaction. Update <name> to your wallet address.
--gas 5000000
refers to the cost of the transaction we are sending. Gas is the unit of cost which we measure how expensive a transaction is.
--chain-id
refers to which chain we are uploading to, which in this case is for Secret testnet!
To verify whether storing the code has been successful, we can use SecretCLI to query the chain:
Which should give an output similar to the following:
In the previous step we stored the contract code on the blockchain. To actually use it, we need to instantiate a new instance of it.
instantiate 1
is the code_id that you created in the previous section
{"count": 1}
**** is the instantiation message. Here we instantiate a starting count of 1, but you can make it any i32 you want
--from <name>
**** is your wallet address
--label
is a mandatory field that gives the contract a unique meaningful identifier
Let's check that the instantiate command worked:
Now we will see the address of our deployed contract
Congratulations! You just finished compiling and deploying your first contract! Now it's time to see it in action!
Learn how to write a full stack decentralized React application utilizing a Secret smart contract and Secret.js
Yao's Millionaires' problem is a secure multi-party computation problem introduced in 1982 by computer scientist and computational theorist Andrew Yao. The problem discusses two millionaires, Alice and Bob, who are interested in knowing which of them is richer without revealing their actual wealth.
The Secret smart contract we will be working with demonstrates an example implementation that allows two millionaires to submit their net worth and determine who is richer, without revealing their actual net worth.
This is the source code for the full stack application, and you can use the live dApp at this web address on Secret testnet.
To interact with the dApp, you will need to have the Secret Testnet (pulsar-3) configured with your Keplr wallet and also fund it with testnet tokens. Learn how to configure and fund your keplr wallet here!
In this demo you will learn how to integrate the Secret Millionaire contract with a front end designed in React using Secret.Js. Let's get started!
This tutorial assumes that you've already learned how to compile, upload, and instantiate a Secret smart contract to the Secret testnet and now you will learn how to connect your instantiated smart contract to a frontend library, namely, React.js.
Excluding the instantiation message, the Secret Millionaire smart contract is capable of executing three messages:
Submit net worth
Reset net worth
Query net worth
Thus, we need to design our frontend in an intuitive manner that will allow the user to execute these messages. By the end of this tutorial you will learn how to do the following:
Integrate Keplr wallet with React.js
Use secret.js to execute multiple transactions simultaneously (submit and reset net worth)
Use secret.js to query the updated Secret Millionaire smart contract
In order to execute the Secret millionaire contract, users must first be able to connect to their Keplr wallet. To see a generalized approach to connecting to Keplr wallet with Secret.js, you can review the Secret.js docs here. However, for our application you will learn how to connect to Keplr with React.js so that the user's wallet can control every page of your app.
Before we proceed, review the ConnectWallet(
)
function:
This function awaits SetupKeplr()
, an asynchronous function which enables Secret Network on Keplr, retrieves the user's account information from Keplr, creates a Secret Network client with this information, and then sets the secretjs
instance and secretAddress
with these details so that we can then share this data with the rest of our application. Notice that when we establish the Secret Network client with Secret.js we are also specifying the url
and chainId
of the client, which in this case is the url
+ chainId
for Secret testnet:
In fewer than 100 lines of code, you have the functionality to connect and disconnect a Keplr wallet and can now use this data in every other part of any dApp you choose to create.
See the completed file here. CreateContext() is the React.js hook that allows us to share the Secret Network client (aka the user's wallet address) throughout the entire application.
Now that a user can connect to our front end, let's write some functions with Secret.js that execute the submit_net_worth()
and reset_net_worth()
functions.
The Secret Millionaire contract is designed such that when a millionaire's net worth is submitted, the contract saves the first two entries and then returns an error for subsequent attempts until a reset. The reset operation resets the contract's state back to its initial condition, where no millionaires are registered.
Because the Secret smart contract requires two millionaires to be submitted at any given time, it would be an improved UI experience if the user could submit two millionaires simultaneously, rather than having to execute two separate transactions. We are able to implement this functionality with Secret.js using MsgExecuteContract and the broadcast
method:
This function takes in two objects, each representing a millionaire, and sends their name and net worth to the Millionaire smart contract using the broadcast
method of the secretjs
library. This method accepts an array of transactions to send to the blockchain and an options object. In this case, the options object specifies a gas limit of 300,000, which is the maximum amount of computational work the transactions are allowed to perform before they are halted. Now let's use React.js to fire this function every time a user clicks on a button.
The handleSubmit
function is a handler for a form submit event in our React application. This function takes user inputs (names and net worths of two millionaires), submits this data to the blockchain, queries the richer millionaire, and updates the UI accordingly:
e.preventDefault();
: This line stops the default form submission event in a web page (which would typically reload the page).
setMillionaire1({ name: name1, networth: networth1 });
and setMillionaire2({ name: name2, networth: networth2 });
: These two lines update the states of Millionaire1
and Millionaire2
respectively. setMillionaire1
and setMillionaire2
are state-setting functions from the useState React hook.
await submit_net_worth({ name: name1, networth: networth1 }, { name: name2, networth: networth2 });
: This line calls the submit_net_worth
function described above, passing two objects containing the names and net worths of two millionaires. It then waits for the function to finish.
await query_net_worth(myQuery);
: This line calls the query_net_worth
function and waits for the function to finish.
setRicherModalOpen(true);
and setShowRicherButton(false);
: These two lines update the states controlling whether a modal and a button are displayed in the UI. They hide a button and show a modal that presents the result of the query_net_worth
function.
alert("Please approve the transaction in keplr.");
: If any error is thrown during the execution of the function, it is caught, and an alert is shown to the user instructing them to approve the transaction in Keplr.
resetSubmit
resets the smart contract state back to 0:
Lastly, we need to be able to query the result of submitted transaction, namely, which millionaire is richer. This function queries the Millionaire contract to get information about who is richer among the previously submitted millionaires, stores the result in the myQuery
array, and then we can display information stored in myQuery
in our front end.
First, we use the queryContract
method of the compute
module from secretjs
library to send a query to the Millionaire contract. This method takes an object as its argument with the following properties:
contract_address
: The address of the smart contract to which the query is being sent.
query
: The query message to be sent to the contract. In this case, it's calling the who_is_richer
method of the contract, which returns information about the richer of the two millionaires.
code_hash
: The code hash of the contract to ensure that the contract code hasn't been tampered with.
The result of this query is stored in the query
variable.
The result of the query is then pushed onto the myQuery
array. This array stores the results of all queries made so far, and we can display this on our React front end.
You now have all of the tools you need to create a decentralized full stack application on Secret Network. In this tutorial you learned how to write React.js functions to connect to Keplr wallet, submit simultaneous transactions and reset net worth data, and query a Secret smart contract for the wealthier party. If you have further questions as you continue to develop on Secret Network, please join the weekly Monday developer call on Discord at 1pmET.
An explainer of the Instantiate file inside of the CosmWasm code framework
The Instantiate function is the function that will run once (and only once) immediately upon initializing your smart contract after uploading it to the network. It is meant to set up the smart contract with all vital code that must be run first before any users have the ability to execute the contract. For those familiar with a solidity constructor on Ethereum, Instantiate serves the exact same purpose.
If you have any vital info to save to the state such as Config parameters or an admin/operator address to name a few options, those should probably be specified here. In addition, if your contract requires entropy for randomization or needs to register to receive a SNIP-20 token for payment, those operations should also be performed here, as other execution messages will rely on that data to be already instantiated.
Instantiate takes four arguments:
"deps"
: allows the contract to interact with the outside world by reading and updating the contract state, accessing other contract states, and using helper functions to work with contract addresses.
"env":
is an object that represents the current state of the blockchain when the contract is executed, including the blockchain's height, timestamp, and the address of the contract being called.
"info"
: metadata about the message that triggered the contract's execution, such as the sender's address and the native tokens that were sent with the message.
"msg":
is the message that triggers the contract's execution, which is currently represented by the "Empty" type in the codeblock above.
A description of CosmWasm and the framework around it as implemented on Secret Network
Smart contracts on Secret Network follow the widely used framework. The framework is built as a module for the Cosmos SDK and allows developers to build smart contracts with which compile to (Wasm). This type of efficient and fast binary, combined with the power of a low-level typed coding language, makes CosmWasm a secure and efficient way for developers to write smart contracts. Contracts on Secret have a public binary just like any other smart contract network, only the contract input, output and state is encrypted. The contracts are executed in the Wasm VM as deployed via the Wasmd Cosmos SDK module on the Secret blockchain.
Secret Contracts slightly differ from any standard CosmWasm implementation as they handle encrypted data, secure enclaves, and the key design around that. Secret currently emulates CosmWasm v1.1.9 and is completely compatible over IBC with other V1.0+ Cosmos Smart contract networks.
Want to learn about how Secret handles the Contract-state encryption and other details that relate to how CosmWasm works on Secret? -- Read the implementation documentation !
A Secret contract has 4 different components as listed below each serving a different function. A deeper overview of the implementation of these 4 components/files is given in the Contract-components section.
An explainer of the Execute file inside of the CosmWasm code framework
Execution Messages are contract messages that trigger the execution of a smart contract and perform specific operations, such as updating the contract state or transferring tokens. If you’re familiar with RPC or AJAX, you can think of execution messages as the code that runs when a remote procedure is called. On Secret Network, execution messages are usually designed to be functions that run as quickly as possible and exit as early as possible when any errors are encountered, as this will help save gas. You can learn more about contract optimization .
The standard practice on Secret Network is to have an enum
with all the valid message types and reject all messages that don’t follow the usage pattern dictated in that enum; this enum is conventionally called ExecuteMsg
and is usually in a file called msg.rs
As mentioned earlier, the standard way to choose what the contract should execute is to look at which ExecuteMsg
is passed to the function. Let’s look at an example.
In this simple example, the code looks at the message passed, if the passage is the Increment
message, it calls the function try_increment
, otherwise, if the message is the Reset
message, it calls the function try_reset
with the count.
Remember, Rust has implicit returns for lines that don’t end in a semicolon, so the result of the two functions above is returned as the result of the execution message.
You may have noticed that the execution message takes two arguments in addition to the msg: deps
and env
. Let’s look at those more closely.
As the name perhaps implies, env
contains all the information about the environment the contract is running in, but what does that mean exactly? On Secret Network the properties available in the Env struct are as follows:
block: this contains all the information about the current block. This is the block height (height
), the current time as a unix timestamp (time
) and the chain id (chain_id
) such as secret-4 or pulsar-3.
message: contains information that was sent as part of the payload for the execution. This is the sender, or the wallet address of the person that called the handle function and sent_funds which contains a vector of native funds sent by the caller (SNIP-20s are not included).
contract: contains a property with the contract’s address.
contract_code_hash: this is a String containing the code hash of the current contract, it’s useful when registering with other contracts such as SNIP20s or SNIP721s or when working on a factory contract.
Now that you have an overview on what all those properties are, let’s take a look at a simple Execution Message.
The execution message above reads the state from the storage(Deps
) and increments the count property by one, then prints out the new count, if successful.
The developers of this contract opted for using storage singletons. A storage singleton can be thought as a prefixed typed storage solution with simple-to-use methods. This allows the developer to define a structure for the data that needs to be stored and handles encoding end decoding it, so the developer doesn’t have to think about it. This is how it’s implemented in this contract:
The config
function returns a singleton over the config key using the State
struct as its type, this means that when reading and writing data from the storage, the singleton automatically serializes or deserializes the State struct.
You may have noticed that the return type for a handle message is Response
. Let's take a look at how Response
is defined:
In CosmWasm, the Ok
result and the Response
object are essential parts of handling contract execution and signaling the outcome to the blockchain. Here's a breakdown of how these work:
Ok
Result in CosmWasmIn Rust (and by extension, CosmWasm), functions that interact with the blockchain often return a Result
type. This is a way to express whether the function was successful or if it encountered an error.
Result<T, E>
: A Result
can either be:
Ok(T)
: Signifying the function executed successfully and returns a value of type T
.
Err(E)
: Signifying that an error of type E
occurred.
In the context of CosmWasm, the Ok
result generally signifies that a contract's execution completed successfully, returning a Response
object.
For example:
This line means that the contract execution was successful, and it returns a default Response
. In CosmWasm, we use StdResult<Response>
for contract execution functions.
- the logic that is ran once on initialization of the contract. This will usually be the initial conditions and configuration of the contract.
- this is where the transactional logic resides. These are functions that modify contract data
- these are functions that are ran as read-only and cannot modify contract data
- the long-term storage of the contract. By default, data used by contracts will be lost between transactions.
Each contract will contain these basic building blocks, with their logic being built around how they work. You can read more about the interplay of Entry-points as depicted in the graphic below in
Want to get started with CosmWasm development? - Dont have much time? - - It teaches everything about the CosmWasm framework, message types and environment setup in a single page.
As mentioned previously, developers don’t really like working with raw binary data, so many resort to using more human friendly ways; you can find out more on the page about storage, . But for the sake of this example, let’s look at what that config
function is doing.
Before we understand what the PrefixedStorage struct is, we should first understand the problem they attempt to solve. We may be tempted to think that the key-value model as described in Storage solves all our storage needs. And we'd technically be correct in thinking that. It is possible to efficiently store vast amounts of user data by just using the methods described in that page. However, we will see that doing this would be cumbersome for the developer in many cases. That's why there are many additional storage structures built on top of the methods described in Storage. Prefixed storage is an additional structure built on top of these methods for the convenience of the developer.
We will go over a storage problem and discuss how we might tackle it with the methods we learnt in Storage to get to the root of what prefixed storage actually is. Suppose that we have contract that stores a different password (String) for each wallet address that interacts with it (so that the wallets can later give that password when querying the contract). How would we want to store all these passwords so that they can be checked when it is time for queries?
Our first approach might be to use a hashmap that stores all the passwords, and save it as binary using the methods we already know. So we would add the following lines to the init
And then we can proceed to load, and add each users' password whenever a user sends the create_password
HandleMsg. Note that I'm assuming we would generate the &[u8]
key for the hashmap from the user's wallet address. This approach has a huge problem! That is whenever we want to add another wallet's password to the hashmap, we must load the entire hashmap with all the passwords stored inside it. This will increase gas costs as we gain thousands of users. Moreover, loading the hashmap for queries will strain the node.
We now realize that we can actually generate&[u8]
storage keys for each user wallet and save the password directly to deps.storage
as described in Storage. This way, we can use may_load
to check if a user has a password, and if he does, learn what it is without loading all the other passwords. Then we would use the following lines of code to save a password
This method does work! However, what if we want to save additional things that is associated to each user, such as token balances. Then using save on the same key would overwrite our previous data. The solution is to add a prefix to the storage key so that we know it belongs to the password property. One way to implement this is the following:
This works but can be cumbersome and ugly, especially if you need to build more functionalities around this idea. This is exactly what prefixed storage does in the background! Prefixed Storage is built to solve this problem while hiding away all the ugliness.
PrefixedStorage is a struct that keeps track of all the storage keys that are used to store various data under a shared namespace similar to described above. Prefixed storage is often used to keep track of storage keys of deps.storage
, but it can in fact be used to store the keys of other storage structures.
The compiler only allows one mutable reference to the underlying storage to be valid at one point. Thus, you cannot have more than one mutable Prefixed Storage objects at the same time.
Let's solve the password problem that was mentioned above with prefixed storage. The solution to this problem resembles viewing keys for permissioned viewing. You will learn more about this in the coming sections. We first define a namespace (prefix) in state.rs
.
The idea then is that all the wrapper functions that we've learnt in Storage also work on prefixed storage. We will only demonstrate save
and may_load
by example.
We would instantiate a mutable PrefixedStorage object using the following lines of code to save a new password
Notice that we are using the same storage wrapper functions from Storage on prefixed storage.
The reason why we can use the same wrapper functions is because Prefixed Storage implements the Storage trait, even though most of what it's doing is to add a prefix on top of the keys we provide to it.
We may use both load
and may_load
We may load data from a mutable Prefixed Storage instance with the following lines of code
In queries, we don't want to use PrefixedStorage struct. Instead we use ReadonlyPrefixedStorage struct. It works in the exact same way, except that you cannot save to it.
You can have as many ReadonlyPrefixedStorage objects as you want at the same time, unlike PrefixedStorage.
We can use the remove
wrapper function for this.
Working with a Singleton in CosmWasm
In CosmWasm smart contracts, a Singleton
is a specialized structure that combines the concepts of PrefixedStorage and TypedStorage, allowing you to store and manage a single data entity efficiently under a specific storage key. This is particularly useful when you need to store just one instance of a state (such as contract configuration or global data) without the need for complex key management.
How Singleton Works:
storage: &'a mut dyn Storage
: The Singleton
holds a mutable reference to the storage layer, which is where the contract data is stored on-chain. This reference allows it to interact with and modify the stored data.
key: Vec<u8>
: The Singleton
operates on a single storage key, provided during its instantiation. This key is typically transformed using to_length_prefixed
to avoid collisions with other stored data, ensuring that the contract can safely manage this unique piece of state.
data: PhantomData<T>
: Rust’s PhantomData
is a marker for the data type T
without actually holding an instance of it. This allows the Singleton
to enforce that it works with a specific type (T
) that must implement Serialize
and DeserializeOwned
traits, which are essential for encoding/decoding the data stored in the singleton.
No Need for Multiple Keys: A Singleton
simplifies storage management by removing the need to manually manage multiple keys. The storage key is predefined in the constructor, and all operations (read, write, update) are tied to this single key. This ensures you are only ever working with one data object in storage.
Type-Safe Storage: The Singleton
uses TypedStorage functionality, which ensures that the data stored and retrieved from the storage is type-safe. This means you always work with the exact type you’ve defined (in this case, T
), reducing the risk of errors during serialization and deserialization.
Avoids Key Collisions: By applying the to_length_prefixed
transformation to the given storage key, the Singleton
ensures there are no name collisions in storage. This is particularly useful in larger contracts where multiple data points might otherwise share similar key names.
There are common wrapper functions that are included in the state.rs
of most secret contract templates, which are save,
load,
may_load,
remove,
and update
.
save
The save
function overwrites previously saved data associated to that storage key. save
will serialize the model and store, returns an error on serialization issues:
Purpose: It takes a reference to the storage and the data to be saved (usually the contract's state).
Usage: You call save
when you want to persist new data or update existing data in storage.
load
This function reads or retrieves data from the contract's state. It is commonly used when you need to access a piece of data stored in the contract, such as the current state or configuration.
Purpose: It accesses the storage and loads the state associated with the provided key.
Usage: load
is used whenever the contract needs to read existing data, such as fetching the configuration or state before making any modifications.
may_load
Purpose: This function attempts to retrieve and deserialize data from storage, returning Ok(None)
if no data exists at the given key. It returns an error only if deserialization fails.
Usage: may_load
is used when the existence of the data is optional. It allows you to safely check if data exists and handle cases where the data might not be present.
Behavior: It fetches the data associated with the key and attempts to deserialize it using may_deserialize
. If the key does not have any data, it returns Ok(None)
instead of an error, making it more forgiving than load
.
remove
Purpose: This function deletes or removes data associated with a specific key from the contract’s storage.
Usage: remove
is used when you no longer need to store certain data in the contract and want to free up space by removing the key-value pair from storage.
Behavior: It accesses the storage
object and deletes the data associated with the provided key.
update
This function loads, modifies, and then saves data back to the contract's state. It’s commonly used when the contract state needs to be modified, such as incrementing a counter or updating a setting.
Purpose: It allows for atomic updates to the state by accepting a closure (a function passed as an argument) that modifies the data and then saves it back to storage.
Usage: update
is ideal for changing existing data in the state, like incrementing a value, while ensuring that the storage operation is performed safely in one transaction.
save
: Persists or updates state in the contract’s storage.
load
: Retrieves and reads existing data from the contract’s storage.
may_load
: Fetches and deserializes data if it exists, returning Ok(None)
if no data is found at the key, and an error if deserialization fails.
remove
: Deletes the data associated with a specific key from storage.
update
: Modifies existing data in storage, allowing for atomic and safe updates.
These wrapper functions provide clean abstractions to manage the contract's state, making the contract logic easier to write and maintain. They are especially useful in ensuring that storage operations (which are critical for on-chain data) are handled efficiently and consistently across the contract.
This hashmap-like storage structure uses generic typed keys to store objects.
A Keymap is Secret toolkit hashmap-like storage structure that uses generic typed keys to store objects. It allows iteration with paging over keys and/or items without guaranteed ordering, although the order of insertion is preserved until you remove objects.
An example use-case for a keymap is if you want to contain a large amount of votes and iterate over them in the future. Since iterating over large amounts of data at once may be prohibitive, a keymap allows you to specify the amount of data that will be returned in each page. We will implement this voting example below to show how keymaps can be utilized in your own projects.
To import this package (and also the packages that we will be using for unit tests), add the following dependencies to your Cargo.toml
file
To import and initialize a keymap, use the following packages in your test environment:
Now let's write our first test!
Let's start by creating a function that inserts key-value pairs into a keymap. This code defines a test function called test_keymap_perf_vote_insert()
that creates a new Keymap
that maps Vec<u8>
keys to String
values, and then inserts 1000 key-value pairs into the Keymap
. Each key is a Vec<u8>
containing an integer from 0 to 999, and each value is a String
containing the text "I vote yes".
This test is passing! Which means that it asserts that the Keymap
contains 1000 key-value pairs, which map a number from 0 - 999 with the string "I vote yes."
Iterating with Keymaps
There are two methods that create an iterator in Keymap. These are .iter
and .iter_keys
. iter_keys
only iterates over the keys whereas iter
iterates over (key, item) pairs. Let's use iter
to test the iterator functionality of aKeymap
that maps Vec<u8>
keys to a struct Vote.
We are going to create a new Keymap
that maps Vec<u8>
keys to a struct Vote
that has two fields, vote
and person
. It then inserts two key-value pairs into the Keymap
, where the keys are the byte vectors b"key1".to_vec()
and b"key2".to_vec()
, and the values are Vote
structs containing information about the vote and the person who cast it.
We then use iter()
to check that the size of the iterator is 2, which means there are two key-value pairs in the Keymap:
Our test is passing! This means that the first element returned by the iterator matches the expected key-value pair for key1
, and that the second element returned by the iterator matches the expected key-value pair for key2
.
For further examples demonstrating the usage of keymaps, refer to the Secret Toolkit repo here.
An explainer of the query file inside of the CosmWasm code framework
Query messages retrieve the relevant smart contract and execute the query message. The results of the query are then returned to the sender of the query message, providing users with information about the state of the blockchain and the smart contracts running on it.
Contracts can define query functions, or read-only operations meant for data-retrieval. Doing so allows contracts to expose rich, custom data endpoints with JSON responses instead of raw bytes from the low-level key-value store. Because the blockchain state cannot be changed, the node can directly run the query without a transaction.
Users can specify which query function alongside any arguments with a JSON QueryMsg
. Even though there is no gas fee, the query function’s execution is capped by gas determined by metered execution, which is not charged, as a form of spam protection.
An explainer on the varying storage frameworks for Secret contracts
CosmWasm storage uses a key-value storage design. Smart contracts can store data in binary, access it through a storage key, edit it, and save it. Similar to a HashMap, each storage key is associated with a specific piece of data stored in binary. The storage keys are formatted as references to byte arrays (&[u8]
).
One advantage of the key-value design is that a particular data value is only loaded when the user explicitly loads it using its storage key. This prevents any unnecessary data from being processed, saving resources.
Any type of data may be stored this way as long as the user can serialize/deserialize (serde) the data to/from binary. Doing this manually every single time is cumbersome and repetitive, this is why we have wrapper functions that does this serde process for us.
All the data is actually stored in deps.storage
, and the examples below show how to save/load data to/from there with a storage key.
Creating a storage key is simple, any way of generating a constant &[u8]
suffices. Developers often prefer generating these keys from strings as shown in the example below.
For example, the above key is likely used to store some data related to core configuration values of the contract. The convention is that storage keys are often all created in state.rs
, and then imported to contract.rs
. However, since storage keys are just constants, they could be declared anywhere in the contract.
The example above also highlights that storage keys are not meant to be secret nor hard to guess. Anyone who has the open source code can see what the storage keys are (and of course this is not enough for a user to load any data from the smart contract).
One common technique in smart contracts, especially when multiple types of data are being stored, is to create separate sub-stores with unique prefixes. Thus instead of directly dealing with storage, we wrap it and put all Foo
in a Storage with key "foo" + id
, and all Bar
in a Storage with key "bar" + id
. This lets us add multiple types of objects without too much cognitive overhead. Similar separation like Mongo collections or SQL tables.
Since we have different types for Storage
and ReadonlyStorage
, we use two different constructors:
Please note that only one mutable reference to the underlying store may be valid at one point. The compiler sees we do not ever use foos
after constructing bars
, so this example is valid. However, if we did use foos
again at the bottom, it would properly complain about violating unique mutable reference.
The takeaway is to create the PrefixedStorage
objects when needed and not to hang around to them too long.
As we divide our storage space into different subspaces or "buckets", we will quickly notice that each "bucket" works on a unique type. This leads to a lot of repeated serialization and deserialization boilerplate that can be removed. We do this by wrapping a Storage
with a type-aware TypedStorage
struct that provides us a higher-level access to the data.
Note that TypedStorage
itself does not implement the Storage
interface, so when combining with PrefixStorage
, make sure to wrap the prefix first.
Beyond the basic save
, load
, and may_load
, there is a higher-level API exposed, update
. Update
will load the data, apply an operation and save it again (if the operation was successful). It will also return any error that occurred, or the final state that was written if successful.
Since the above idiom (a subspace for a class of items) is so common and useful, and there is no easy way to return this from a function (bucket holds a reference to space, and cannot live longer than the local variable), the two are often combined into a Bucket
. A Bucket works just like the example above, except the creation can be in another function:
Singleton is another wrapper around the TypedStorage
API. There are cases when we don't need a whole subspace to hold arbitrary key-value lookup for typed data, but rather a single storage key. The simplest example is some configuration information for a contract. For example, in the name service example, there is a Bucket
to look up name to name data, but we also have a Singleton
to store global configuration - namely the price of buying a name.
Please note that in this context, the term "singleton" does not refer to the singleton pattern but a container for a single element.
Singleton
works just like Bucket
, except the save
, load
, update
methods don't take a key, and update
requires the object to already exist, so the closure takes type T
, rather than Option<T>
. (Use save
to create the object the first time). For Buckets
, we often don't know which keys exist, but Singleton
s should be initialized when the contract is instantiated.
Since the heart of much of the smart contract code is simply transformations upon some stored state, we may be able to just code the state transitions and let the TypedStorage
APIs take care of all the boilerplate.
CosmWasm storage is built on a key-value storage design, similar to a HashMap, where data is stored in binary format and accessed using storage keys represented as byte arrays (&[u8]
). Developers can serialize and deserialize data to/from binary, simplifying this process through wrapper functions. Storage keys are typically constants declared in state files and are not meant to be secret.
Prefixed storage allows for creating separate sub-stores with unique prefixes for organizing different data types, while Typed Storage provides a type-aware interface to reduce serialization boilerplate. CosmWasm also supports advanced abstractions like Buckets and Singletons. Buckets create subspaces for storing collections of items, while Singletons manage single elements, typically used for global contract configuration data. These APIs streamline storage handling, enabling efficient data management in smart contracts.
Now, we will explore some of these storage methods, such as PrefixedStorage
, Singleton
, and Keymap
, in more depth to see how they interact with on-chain data.
An explainer of the dependecies inside of the CosmWasm code framework
Deps
(or the mutable version DepsMut
) holds all external dependencies of the contract, and is designed to allow easy dependency injection at runtime. Those external dependencies are as follows:
As the name suggests, storage allows reading and writing data in a contract's memory. Secret Network contracts use a key-value store for reading and writing data and provide three easy-to-use methods to do so:
get(&self, key: &[u8]) -> Option<Vec<u8>>
reads data stored in the specified key
set(&self, key: &[u8], value: &[u8])
which writes data to the storage in the specified key
remove(&self, key: &[u8])
which deletes a key alongside its data.
You may have noticed that storage uses byte arrays exclusively to read and write data, so to make it more user-friendly there are certain libraries that the community has developed. You can learn more about this here.
Often the question of, "How much data can be stored in a contract?" is asked. The answer is that it’s complicated. Technically there’s no upper-bound on how much data a contract can store, but there are practical per-transaction limits.
Reading one byte of data “uses” 3 gas and writing one byte of data “uses” 30 gas, so with the current limit of 6_000_000 gas per block, the practical limit is that a contract can read — at most — 13.33Mb of data per transaction and can write — at most — 1.3Mb of data per transaction. This assumes that the contract doesn’t do anything but read and write data, the more complex the contract, the lower the amount of data you can read and write. Note that the limits are per transaction, you could write several gigabytes of data if you wanted to but it would take several transactions.
Api consists of a collection of callbacks to system functions defined outside of the wasm modules. Those are functions that are defined in the chain’s code, this allows calling useful functions without needing to include them in the contract’s code and assures a smaller binary size as well as a higher certainty that they’re going to be implemented correctly.
canonical_address(&self, human: &HumanAddr) -> StdResult<CanonicalAddr>
Takes a human readable address and returns a binary representation of it.
human_address(&self, canonical: &CanonicalAddr) -> StdResult<HumanAddr>
Takes a canonical address and returns a human readble address. This is the inverse of canonical_address
secp256k1_verify(&self, message_hash: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool, VerificationError>
Verifies message hashes (hashed unsing SHA-256) against a signature, with the public key of the signer, using the secp256k1 elliptic curve digital signature parametrization / algorithm.
secp256k1_recover_pubkey( &self, message_hash: &[u8], signature: &[u8], recovery_param: u8 ) -> Result<Vec, RecoverPubkeyError>
Recovers a public key from a message hash and a signature.
ed25519_verify( &self, message: &[u8], signature: &[u8], public_key: &[u8] ) -> Result<bool, VerificationError>
Verifies messages against a signature, with the public key of the signer, using the ed25519 elliptic curve digital signature parametrization / algorithm.
ed25519_batch_verify( &self, messages: &[&[u8]], signatures: &[&[u8]], public_keys: &[&[u8]] ) -> Result<bool, VerificationError>
Performs batch Ed25519 signature verification.
Batch verification asks whether all signatures in some set are valid, rather than asking whether each of them is valid. This allows sharing computations among all signature verifications, performing less work overall, at the cost of higher latency (the entire batch must complete), complexity of caller code (which must assemble a batch of signatures across work-items), and loss of the ability to easily pinpoint failing signatures.
This batch verification implementation is adaptive, in the sense that it detects multiple signatures created with the same verification key, and automatically coalesces terms in the final verification equation.
secp256k1_sign( &self, message: &[u8], private_key: &[u8] ) -> Result<Vec, SigningError>
Signs a message with a private key using the secp256k1 elliptic curve digital signature parametrization / algorithm.
ed25519_sign( &self, message: &[u8], private_key: &[u8] ) -> Result<Vec, SigningError>
Signs a message with a private key using the ed25519 elliptic curve digital signature parametrisation / algorithm.
(CosmWasm 1.0) debug
- a function that allows for debug prints, which can be used during development to print to STDOUT in a testnet or LocalSecret environment (replaces debug_print
from CosmWasm v0.10)
(New in Secret-Cosmwasm v1.1.10) gas_evaporate(evaporate: u32) -> u32
- this API function allows a contract to consume a set amount of gas. Use together with check_gas
in order to create contract calls that consume an exact amount of gas regardless of the code path taken. Documentation is here.
(New in Secret-Cosmwasm v1.1.10) check_gas() -> u64
- this API returns the current amount of gas consumed by a contract call.
Optimizing your code is especially important on blockchain due to the fact that inefficient code costs your users money and can jam up the entire network. With any function you make, try to complete it in as few steps as possible and avoid unnecessary computation. One of the most costly actions you can make is loading and saving data, thus you should ensure you are only saving what is needed. Aside from this, there are a few other tips we can provide.
When dealing with large sets of data, vectors and arrays can be especially problematic because in order to save load a single item within them, you must effectively load the entire vector and all of its contents, which naturally can amount to an enormous amount of inefficiency when you only need one item! There are several ways around this: Keymap, AppendStore, or even deconstructing vectors into a set of data keyed to a counter variable. Research each options pros and cons, and choose which one is best for your particular contract.
A high-level overview of Secret smart contracts
Learn how to create a SNIP-20 token on Secret Network
In this tutorial, we are going to create our own SNIP-20 token on Secret Network using Secret Labs' SNIP-20 reference implementation contract, and we will learn how to upload, instantiate, execute, and query our SNIP-20 contract using Secret.js. Let's dive in!
You can clone the source code , which we will reference throughout the course of this documentation.
Use to set up your developer environment.
Now that you've cloned the SNIP-20 reference implementation repo above, let's compile the contract. In your terminal run make build-mainnet-reproducible
.
The compiled wasm file will be output in ./optimized-wasm
.
Now let's upload and instantiate the SNIP-20 Contract. We have already written an for your convenience. If you would like to make any changes to your SNIP-20 token, you can do so .
The initMsg
object in our upload.js
file is referencing the instantiation message defined in . Notice the optional config
variable. If we include config
, there is a variety of additional contract functionality that we could program, such as burn, mint, admin privileges, etc.
Now we are going to instantiate some ZBRA coin. If you want to create your own coin name, update the name, symbol,
and amount
fields respectively.
Otherwise, run node upload:
Upon successful execution, a codeId, contract hash, and contract address will be returned:
To check that the instantiation of our SNIP-20 ZEBRA token was successful, let's query the smart contract's token info.
Run node query_token_info
:
The following is returned upon successful query:
The reason total supply
is null
is because we chose to make total supply
hidden in our instantiation message. If you want it to be public, then in the InitConfig
variable set public_total_supply
to true.
Now let's execute the transfer message with secret.js. Be sure to update the recipient
wallet address with your own wallet before executing the code below. For testing purposes, I am using two Keplr wallet connected to the Secret Network testnet in order to move funds back and forth:
Congrats! You just successfully transferred your own SNIP-20 token on Secret Network! 🎉
Explanation of executing modules inside a CosmWasm contract using sending Native SCRT as an example.
Sending Native SCRT from a contract is realtively trivial and can be done using the message. This is a call. Any native denoms can be send in this way (ex. native IBC tokens).
Having Trouble? Always make sure your contract has enough funds to begin with!
You can send some coins to your contract like this:
You can always see decrypted error messages from the contract like so:
You can see the spent coins as events when you query for the transaction:
AppendStore is meant to replicate the functionality of an append list in a cosmwasm efficient manner. The length of the list is stored and used to pop/push items to the list. It also has a method to create a read only iterator.
This storage object also has the method remove
to remove a stored object from an arbitrary position in the list, but this can be extremely inefficient.
❗ Removing a storage object further from the tail gets increasingly inefficient. We recommend you use
pop
andpush
whenever possible.
The same conventions from Item
also apply here, that is:
AppendStore has to be told the type of the stored objects. And the serde optionally.
Every methods needs it's own reference to deps.storage
.
To import and initialize this storage object as a static constant in state.rs
, do the following:
❗ Initializing the object as const instead of static will also work but be less efficient since the variable won't be able to cache length data.
Often times we need these storage objects to be associated to a user address or some other key that is variable. In this case, you need not initialize a completely new AppendStore inside contract.rs
. Instead, you can create a new AppendStore by adding a suffix to an already existing AppendStore. This has the benefit of preventing you from having to rewrite the signature of the AppendStore. For example
Sometimes when iterating these objects, we may want to load the next n
objects at once. This may be prefered if the objects we are iterating over are cheap to store or if we know that multiple objects will need to be accessed back to back. In such cases we may want to change the internal indexing size (default of 1). We do this in state.rs
:
The main user facing methods to read/write to AppendStore are pop
, push
, get_len
, set_at
(which replaces data at a position within the length bound), clear
(which deletes all data in the storage), remove
(which removes an item in an arbitrary position, this is very inefficient). An extensive list of examples of these being used can be found inside the unit tests of AppendStore found in append_store.rs
.
AppendStore also implements a readonly iterator feature. This feature is also used to create a paging wrapper method called paging
. The way you create the iterator is:
More examples can be found in the unit tests. And the paging wrapper is used in the following manner:
An introduction to the differences between standard CosmWasm smart contracts and Secret Contracts
Secret Contracts are an implementation of the Rust-based library CosmWasm, while additionally enabling computation with private metadata. This brings unique use cases to Secret Network which aren’t possible on other blockchains, and also means that Secret Contracts have features that are different from standard CosmWasm contracts including: contract hashes, a specialized approach to iterators, raw queries, and contract migration.
An in-depth analysis of that can be deployed on a vanilla CosmWasm chain as well as Secret Network can be found in our
Contract hashes are required to bind a transaction to the specific contract being called. Otherwise, in a forked chain you could replace the called contract with one that decrypts and prints the input message.
Secret contracts do not have access to standard CosmWasm iterators because keys and values of data are encrypted when stored on-chain. This means that there is no logical structure that can allow iteration over these keys. However, Secret Labs has developed iterator functionality that can be imported using the
Raw queries are not available since raw data is encrypted when stored on-chain. Data can be decrypted only by the contract that owns the data, and so data is only available via permissioned contract queries (smart queries).
CW-plus, a toolkit for CosmWasm contracts is largely supported but might not be optimal to use in all cases. Secret has its own Toolkit called that has the majority of CW-plus functionality. Alternatively one can alter small parts of CW-plus around the missing Iterator feature to leverage that library as well.
Now that we have successfully instantiated our SNIP-20 contract, let's send an to better understand the contract's functionality.
Start by adding the token to your Keplr wallet. Click on Keplr, select the hamburger icon, select "Add Token", and then paste in your token's contract address. If you need to fund your wallet to execute the transaction, you can do so using the . You should now see your token in your Keplr wallet!
Let's to another wallet address. The transfer message is defined in msg.rs as follows:
Your start on privacy design for Secret smart contracts
Secret Network provides all the tools necessary to build fully private decentralized applications, but contracts do not automatically inherit perfect privacy. Privacy has a cost. Developers have both the freedom and the burden of choosing a privacy model that fits their applications' needs.
Protecting sensitive user data is not even the whole story when it comes to Secret dApps. Achieving a proper level of privacy can be mission critical in certain areas, such as competitive gaming, where an information leak could lead to a compromised system, say, if an attacker were able to cheat.
As a developer, it is your responsibility to understand the gamut of privacy risks, their implications, and how to best mitigate them.
Let's start off with a thought experiment. In an ideal world, a perfectly private application would be one that an attacker could not plausibly deduce any information about regarding its state, history, or participants. To an observer, any time a participant reads from or writes to the contract, it would be indistinguishable from every other action.
All outputs would appear to be random and unpredictable, with no discernible patterns that could be used to infer the underlying operations or data. On top of all that, the timing and frequency of executions would need to be consistent across the lifespan of the application.
In reality, there are many forms of contextual data that an attacker can use to infer information, de-anonymize users, or compromise the privacy of applications purely through analysis:
transaction sender's address, their public transaction history, the timing and frequency of their transactions, and their relation to other accounts on the network
public inputs and outputs of an execution, such as sent or received funds (e.g., wrapping/unwrapping SCRT or IBC tokens)
timing of an execution, its temporal locality to other transactions (e.g., clustering, dispersion, co-ocurrence), and an execution's order relative to other on-chain events
size of the encrypted inputs and outputs of executions, shape and size of the event log, and public transaction metadata including gas_used
and nested calls to other contracts
timing, frequency, shape and size of query requests and query responses
storage access patterns when a contract reads from and writes to the encrypted application database
Additionally, there are active attacks that can expose unwanted leaks of information:
executions that produce measurable side-effects
tx replay attacks, side-chain attacks, the Millionaire's Problem
targeted storage access pattern attacks
Its important to note that a few of the risks listed above can only be mitigated by user OPSEC. For example, the timing and frequency of executions, tx sender address, and relations to other accounts are risks typically assumed by end-users. In these cases, education is the best instrument to protect users' privacy.
Some of the other risks should be mitigated by client-side software, such as wallets and dApp front-ends. This includes padding inputs to executions and queries, being strategic about the timing and distribution of query requests, and creating workflows that allow users to more easily create identities that are not linked to their public account.
The remaining privacy risks are those which can only be mitigated through contract design. Developers should strive to make all queries and executions appear indistinguishable from one another to an outside observer. This includes:
padding query and execution results (see pad_query_result
and pad_handle_result
from the Secret Toolkit)
using Gas Evaporation to make all methods consume a consistent amount of gas
adding decoy attributes to the event log when necessary
understanding storage access pattern attacks and employing a system of decoys or oblivious data models (or even UTXO)
Find code implementation of the above privacy preserving methods under our Privacy design section.
Secret network uses Tendermint as a consensus engine combined with the Cosmos SDK, as such it has a relatively high throughput and the possibility for easy horizontal scalability. Because of this, there is no fee market on Secret Network, which means there is no fee-based prioritization built into the standard tendermint binary required to run Secret. Using a higher transaction fee will only increase the number of block signers (validators) that are willing to process your transaction. This might speed up finality but does not prioritize your transaction in the cue.
The fee a user pays for a transaction is based on two things: the computational resources/block space that is used and the fee that validators ask for these resources. These two elements are called gas
and the gas fee
.
The gas
required for the computation is determined by the smart contract engine. The current gas estimates per type of contract interaction are listed here (for SDK components) and here (for enclave components). More complex transactions will consume more gas.
The gas fee
is determined by validators themselves as a parameter in their node service. The min_gas_fee
parameter allows validators to set a minimum fee denominated in uSCRT
. If a transaction has a lower allocated fee, they will not add it to blocks they propose. Validators propose blocks based on random selection following their Voting power. The min_gas_fee
for each block is dependent on the proposing validator. If blocks become too full or an attacker spams the network, the validators can increase their fee.
Secret validators run three tiers of fee's; this choice is up to the validator. Overall, we can assume more than 40% of validators support the "low" fee option, meaning pushing a TX with the low fee often gets processed within a few blocks. Pushing a TX with the "High" fee will increase the chance that the next block can fit your transaction. These TXs are therefore considered "faster". The current recommended fee options are listed in the Cosmos chain registry.
Now let's assume we execute a swap for a single Dex pool. We can expect this to take roughly 150000 Gas
. Executing this at the "low" fee option, we will pay 0.0125 uSCRT
for each Gas
unit used. The total fee for this transaction then becomes: 150000 * 0.0125 = 1.875 uSCRT = 1.875/1000000 = 0.001875 SCRT
. At 40% voting power supporting a min_gas_fee
of 0.0125 uSCRT
we can expect this transaction to be finalized after 4 blocks ( 4*6s = 24s
) with a 97.5% certainty [1- (40%**4) = 0,9744
].
Secret also allows for Gas abstraction for users by leveraging the CosmosSDK FeeGrant module. This module allows one to submit transactions where a different wallet is paying the gas fees as long as they granted you a budget to do that.
Documentation to create Feegrant functionality in your UI are here. This tool is widely used in different Secret UIs (for ex: Secret dashboard) and there is a community run FeeGrant faucet available for dApps to use. - Faucet - Code
This page describes some of the core information required to get up to speed about the state of Secret Contracts in comparison to public (cosmos) networks.
Smart contracts on Secret Network follow the widely used CosmWasm framework. The framework is built as a module for the Cosmos SDK and allows developers to build smart contracts with Rust which compile to WebAssmebly (Wasm). This type of efficient and fast binary, combined with the power of a low-level typed coding language, makes CosmWasm a secure and efficient way for developers to write smart contracts. Contracts on Secret have a public binary just like any other smart contract network, only the contract input, output and state is encrypted.
Secret Contracts slightly differ from any standard CosmWasm implementation as they handle encrypted data, secure enclaves, and the key design around that. Secret currently emulates CosmWasm v1.1.9 and is completely compatible over IBC with other V1.0+ Cosmos Smart contract networks.
Private data (contract input, output and state) on Secret is encrypted using an unforgeable contract encryption key which is unique for every contract. Moreover, the user dedicated state in a contract is only visible to the user themselves. Because of this design, contract migration is a privacy risk. If enabled, a contract admin could upload a malicious contract that reads and prints data entrusted to the old contract. Additionally, iterators and raw queries are not supported as one can not iterate over data stored by a different user as there is no access to their dedicated encryption key.
More information about the differences in implementations for a contract between Vanilla and Secret CosmWasm are listed here.
Secret contract encrypted input and access-controlled output is very powerful and enables many different usecases, combining this with novel development tools creates a powerful framework for developers to leverage. Below some of these tools and ideas are listed to get your brain going.
Privacy as a Service - More utility for other blockchains by leveraging Secret contracts cross-chain. Want to build on polygon, ethereum, Sei, Neutron or Aptos but want additional features only possible with data privacy? You can use Secret contracts cross-chain to keep your application decentralized and offer users new and interesting ways to explore your dApp.
Storage of private/encryption key (fragments) - One can use Secret contracts to store very sensitive info to enable cool usecases like access-controlled content or even cross-chain account ownership for DAOs and unstoppable threshold wallets.
on-chain randomness (secret-vrf) and hidden (game) state - Games on Secret benefit of true on-chain randomness that can remain hiddin inside the contract forever if so desired. Together with a hidden gamestate this brings a whole new dimension and fairness to turn-based games for Web3 like Poker or Mario party and more clarity and ownership to casino games and lootboxes.
Frontrunning resistant and privatized DeFi - because of an encrypted mempool secret is frontrunning resistant by default. Order size - unknown, Placed bids - unknown, liquidation points - unknown and many other features from orderbooks to dark pools have these privacy features as well.
Secret.js - a Javascript library for interacting with the Secret network, Secret contracts, providing encrypted input and creating viewing keys/permits for encrypted output. The library can also be considered as a more complete Cosm.js and can be used on every other cosmos chain. - Alternatives in Python, Kotlin, .Net and more exist as well.
Polar, Fadroma, Secret-toolkit (CW-plus) and Secret IDE - Secret has dedicated tooling sets built by external teams and core development that abstract boilerplate code (for DeFi), help with testing scenarios, simplify contract development nightmares like storage and type handling and even catch errors as you work in a dedicated IDE.
IBC, Interchain accounts/Queries/contracts, Axelar GMP, Bidshop messaging and more - Want to build your application crosschain? Secret has many options for both Message and Asset bridging protocols with the most renowned being IBC (current version IBC-go V4).
The privacy of Secret Network is reliant on secure enclaves, which impacts how the network performs but also how one should design their contracts to enhance privacy. Firstly, it is important to note that only data originating from inside the enclave is verifiable and any data coming from external resources has to be verified before relying on it for execution. This means data coming from contract callbacks is to be trusted, but the sender address is not. If I allow the sender to create an item on-chain, then only the sender should be able to remove that item. Verifying that it is indeed the sender (data owner) that is requesting this change is done by "input data verification". Explanation of the various network level integrated methods is available here.
In practice a Secret contract developer does not have to think too much about this as its abstracted away in the data verification process in the contract module of the network.
When designing contracts for Secret network, one should take into account the usage of so-called replay attacks and other theoretical attack vectors on secure enclave derived privacy. An example of this is the abundant usage of the Contract hash in Secret contract development. The usage of a contract hash is required to link the input to the specific contract being called. Otherwise one could make a forked chain and replace the contract with one that reads and prints the input parameters. Although encryption for Secret contracts is handled by the protocol, developers should still be mindful of their contract design to optimize for these privacy aspects.
One example of such a privacy optimized design would be the use of the Gas evaporation function as added in the v1.9 upgrade and documented here (link coming soon).
There are various types of outputs that can be expected, including:
An updated contract state (i.e., the user’s data should update the state or be stored for future computations)
A computation result encrypted for the transaction sender (i.e., a result should be returned privately to the sender)
Callbacks to other contracts (i.e., a contract is called conditional on the outcome of a Secret Contract function)
Send messages to other modules (i.e., for sending value transfer messages that depend on the outcome of a computation).
Reply - Send more detailed error logs of various submessages as explained here.
Want to dive deeper into the CosmWasm framework? - You can read more about it's design in the CosmWasm book.
Secret Contracts have private input, output and state. This means all data on-chain is stored as cyphertext and is only accessible by the owner (either the contract or the user). More details are available in the encryption specs.
The binary of any contract is public, meaning the execution logic is verifiable just like any other smart contract blockchain.
Secret smart contracts are IBC compatible (CosmWasm v1.0+), allowing for cross-chain logic with other IBC-connected chains.
from v1.11 Secret now supports migrate-able contracts just like vanilla CW, it does not support iterators due to contract privacy design. Workarounds are highlighted under storage.
When designing Secret contracts, the verification of input-data is key to ensure the secure execution of transactions as not all blockchain data is exposed automatically to the enclave.
Developers need to be mindful of data leakage via replay attacks, improper padding, and more so that the privacy for users of their contracts is optimized.
Since all computations are done privately inside the TEE of the network full nodes and the state of the blockchain is encrypted we would expect that users have no access to their own balance, debt positions, tokens, and other important information.
However, because users sign transactions with their own private key the protocol knows they should have access to their information. Viewing keys and Permits are the tools used to provide only the owner access to the private data of their signed transactions.
This part of the documentation will cover the no-code overview of the workings of Permits and viewing keys.
Secret Network uses the Cosmos SDK and its infrastructure which makes it so that the identity of a querier (someone requesting data) cannot be cryptographically authenticated. On public networks this might not be a problem for users as they can query data by using their public key. However, on private networks (where only the owners should have access) this is a problem. To solve this problem, viewing keys were implemented as a part of the SNIP-20 token specification.
Viewing keys act as an encrypted password for the viewing of data related to a specific smart contract and private key. The password can only be created by the private key owner, but anyone with the password who knows the accompanying public key gets access.
To create a viewing key a user signs a transaction for a specific contract (ex sSCRT token), this transaction asks for a random input from which it generates a viewing key. The viewing key is saved in the contract state together with the user's public key (address). To query for private data (ex balance, history) both the viewing key and the accompanying address is required.
Anyone who knows the correct combination of key + address can view the private data without needing access to the private key of the address. Secret Network allows users to maintain control over their data and decide what is shared and with whom.
for more on info on Viewing keys check out the Development section for permissioned viewing and the SNIP-20 specification
This page explains the concept of input data verification, a required mechanism to ensure reliable privacy for secret contracts.
As Secret runs the compute module from within the enclave of the validator node it handles 2 types of data, data coming from within the enclave or outside of it. This data can then be separated as trusted (in-enclave) and un-trusted (outside enclave) meaning you can only rely on the trusted to be true in all cases, the untrusted data can be forged either with replay attacks on forked chains or state modification attacks.
The amount of trusted data options can be increased by additional verification mechanisms.
During execution, some contracts may want to use "external-data", meaning data generated outside of the enclave and sent into the enclave, such as the tx sender address, the funds sent with the tx, block height, etc...
As these parameters are sent to the enclave, they can theoretically be tampered with, and an attacker might send false data; making relying on this data conceivably risky.
For example, let's say we are implementing an admin interface for a contract, i.e. functionality only for a predefined address. We want to ensure the env.message.sender
parameter provided during contract execution is legitimate, so we will confirm that env.message.sender == predefined_address
. If this condition is met we can provide admin functionality. If the env.message.sender
parameter can be tampered with — we effectively can't rely on it and cannot implement the admin interface.
This verification is done by a light-client implemented inside of the enclave which validates all compute transactions and their order. It matches the set of transactions to be executed with the data hash field of the block at the current height. This means we can trust contract executions (Attention: not other modules like Bank), User address, inputs and sent funds are all part of the signed and validated inputs.
The light client does not validate internal contract calls - when your contract calls another during runtime - but these are signed in such a way that it can only be generated by another contract inside a genuine enclave. This can be considered trusted execution as well.
Looking at state modification attacks - these refer to attacks that attempt to modify the internal state of the chain to create behaviour that allows to compromise confidential data. This can be done by changing the internal state values of the chain (even encrypted values can be replaced with other valid encrypted values) and executing the real transactions to see what happens.
For example, imagine a contract that has the following logic:
Receive transaction from user with some amount of SCRT
Validate that payment is exactly 1000 SCRT
If payment is correct, reveal secret value
An attacker can use a modified chain to attack this logic in the following way:
Send a transaction on the real chain with 1000 SCRT from an address that has 1 SCRT
Wait for the transaction to fail on mainnet
On a modified chain fund the attacker's address with 1000 SCRT
Replay the real block from mainnet
Reveal the secret value
This scenario illustrates how payment systems need to consider these attack values.
Protecting against modified state is being actively developed, and will be solved by validating that values that are read from the chain are part of the merkle tree of the chain itself - Coming end of 2023
Trusted = No
means this data can easily be forged. An attacker can take its node offline and replay old inputs. This data that is Trusted = No
by itself cannot be trusted in order to reveal secrets. This is more applicable to init
and handle
, but know that an attacker can replay the input msg
to its offline node.
The equivalent of padding for gas_used
When a smart contract is executed, CosmWasm meters how much gas the execution consumes while the program is running. On Secret Network, this all happens within the enclave. However, once execution completes, the total amount of gas that was consumed leaves the enclave and is made public in transaction metadata.
The public gas_used
result of a contract execution message reflects the sum of costs for the actual instructions that were executed. Within a typical smart contract, most of its methods consume different amounts of gas. With SNIP-20 for example, an attacker could deduce which method a transaction executed with a high degree of certainty; create_viewing_key
vs. transfer
vs. increase_allowance
, each of which produce a distinct gas_used
amount in the public transaction metadata.
In some cases, this can leak even more information such as which code path was taken, the value of a conditional, how many iterations a loop completed, and so on.
Evaporation is a concept that was introduced to overcome the privacy risks associated with gas_used
. Evaporation refers to the practice of deliberately consuming extra gas during execution in order to pad the gas_used
amount before it leaves the enclave.
With evaporation, clients can now instruct contracts on exactly how much gas their execution should consume, yielding a consistent gas_used
across all methods.
A contract can evaporate an arbitrary amount of gas using the built-in API function gas_evaporate
. In order to evaporate any remaining gas, the check_gas
API function allows contracts to calculate the difference between the amount consumed up to that point, and the amount the client wants the contract to consume by the end of its execution:
See the evaporation example contract's for more details.
Note that there is no API function to get the amount of actual gas remaining. Instead, that information must come from the client. This practice ensures that users are still able to execute multiple messages in a single transaction by specifying gas limits to each contract execution individually.
The new check_gas
API function essentially gives contracts read access to CosmWasm's internal gas metering. Calling this function more than once during execution allows contracts to track changes in the amount of gas consumed. This simple feature enables a realm of interesting use cases not previously possible.
In its simplest form, gas tracking can be used as a development aid to inspect the amount of gas used by certain blocks of code, giving developers more insight and help with optimizing their gas footprint.
A more advanced use case involving gas tracking is opportunistic execution, where contracts take advantage of excess gas that would otherwise be evaporated by performing work that needs to be done anyway. That work could be anything from generic housecleaning duties, such as processing items in a queue, to more intentional duties, such as notarizing previous user actions to protect against tx-replay attacks.
To give an example, imagine an NFT auction contract that accepts bids on listed items. When a listing expires, the listed NFT should be transferred to the highest bidder. In typical contract design, the transfer would only actually happen once the winning bidder calls a method to "claim" their winnings. However, with opportunistic execution, the contract can automatically perform the transfer during any execution. For example, someone creating a viewing key could end up paying the gas fees to complete the transfer of a previous listing's NFT to its winning bidder.
An in-depth explanation of Secret VRF, a secure and verifiable random number generator
Want to develop with decentralized on-chain randomness?
The ability to generate fair and verifiable random numbers on blockchain without compromising security or usability is critical for many decentralized applications. However, generating true randomness is difficult because blockchains rely on consensus mechanisms, such as Proof of Work or Proof of Stake, to reach agreement on the state of the network, and these consensus mechanisms are deterministic and therefore do not provide a source of true randomness. There have been attempts to solve this problem by various methods, such as using random data from external sources (the hash of a previous block, etc) or by using off-chain solutions, such as Chainlink’s VRF oracle, but these solutions are not optimal as they are either deterministic, rely on trusted parties, and/or require additional fees for gas and infrastructure. What is needed is a verifiable random number generator that exists on-chain, and this is now possible across blockchain ecosystems through Secret Network Random Number Generator (RNG).
The ability to generate true randomness is a fundamental requirement for blockchains. Verifiable randomness guarantees that operations such as the minting of NFTs, on-chain games, and DAOs are equitable and secure. In the case of NFT minting, randomness allows for features such as unordered minting, trait randomization, and identity numbering, all of which are critical for verifying the authenticity and security of NFT collections. In Web3 gaming, components such as gambling, damage calculation, loot boxes, boss drops, etc, rely on randomness to create trust amongst players and ensure that no player has an unfair advantage. And in the case of DAO tooling, randomness is required for features such as wallet initialization, task assigning, unordered voting/liquidations, order book ordering, etc. By using Secret Network RNG in these and other applications, the Interchain can help to ensure the security, transparency, and fairness of blockchain operations, which in turn promotes the growth and adoption of the Cosmos.
Secret Network is built on top of Tendermint, a peer-to-peer networking protocol that provides Byzantine-Fault-Tolerance (BFT) and Proof-of-Stake (PoS) consensus. This consensus ensures that all nodes maintain the same blockchain and that all nodes have the ability to propose new blocks. From a high-level standpoint, a new consensus round is:
Initiated with a Proposer Node
The Proposer selects transactions in the mempool to be included in a new block
This proposed block is broadcasted to all nodes (Pre-vote phase) and nodes verify that the block is valid, simultaneously verifying that the Proposer is also valid and sign the pre-vote message
If >2/3 of nodes pre-vote for this block, the block is deemed valid
Once a block is committed, there is no way to revert it and there is instant finality
The process of block proposal and execution involves a crucial intermediary step known as the "prevote phase." Secret Network RNG creates verifiable on-chain randomness by generating a random number that is attached to each Proposer’s block proposal, and this random number can then be used for on-chain randomness for the current block and exhibits the following properties:
The number is generated with sufficient entropy
The block proposer cannot predict or influence the entropy generation
Outside actors cannot predict random numbers before they are used to compute network transactions
Secret Network achieves this by implementing the following privacy network consensus:
Secret Network is unique from other blockchains because it utilizes Software Guard Extensions (SGX) to provide a Trusted Execution Environment (TEE) by creating a secure area in memory, called an "enclave," where sensitive data and code can be processed in a way that is isolated from the rest of the blockchain. This means that even if the operating system, other applications, or an attacker were to somehow compromise the security of the blockchain, the data and code within the SGX enclave remains protected.
Secret Network’s SGX allows for private computation on-chain and uniquely positions Secret Network to be the privacy provider of the interchain. In order to provide randomness as an interchain service, Secret Network’s SGX generates a random number on-chain during the block proposal stage of Tendermint. Block proposers generate a random number inside of the Trusted Execution Environment using RDRAND/RDSEED instruction.
RDRAND and RDSEED are Intel Secure Key technology instructions used in computer processors to generate random numbers
The RDRAND instruction is used to generate random numbers, while the RDSEED instruction is used to generate seed values for cryptographic purposes. Externally influencing RDRAND that is executed inside an SGX enclave is very difficult to do, with no known practical or theoretical attacks that can reduce the resulting entropy of the instructions.
In addition to generating random numbers using SGX, Secret Network adds an additional layer of security to the output of the random number generation by pairing it with an Initial Randomness Seed (IRS). This seed originates from a shared consensus seed among all network nodes, and the random number and IRS are combined using an algorithm called HKDF(rfc5869) to certify that the final number is truly random and cannot be predicted by anyone, even if they were to influence the original random number generation.
Secret Network also includes an additional key that is available to all nodes on the network within the TEE called the Random Encryption Key (REK). The REK is used to encrypt the generated random number using symmetrical encryption, and the encrypted blob (Erand) is attached as a new field to the block proposal. The REK is derived from the consensus seed, so all network nodes can derive it without the need for key-sharing protocols.
In order to make sure the random number generated by the block proposer is secure, a majority of network validators must signal their approval of the proposed block before the encrypted number can be decrypted. Tendermint’s pre-vote mechanism allows the blockchain to “signal” that the proposed block has been authorized by a majority of network validators, and guarantees attempted execution of the block. Therefore, the only way to decrypt the Initial Randomness Seed is for nodes to submit a 66% majority of pre commits/commits to the TEE. The TEE will then verify this majority and decrypt the seed, returning it to the caller in its decrypted state, which can be then used for network randomness.
To prevent errors or malicious behavior, the encrypted number will be accompanied by a proof, which is defined so that an SGX enclave can verify that the proposed encrypted random number matches the block height and active set of the current Tendermint block. The proof is created using a one-way-function that takes the encrypted random value, the block height, active validator set, and the Random Encryption Key (known only to network nodes). With the proof key, SGX enclaves can verify that the proof was made within the enclave.
Secret Network’s TEE is continuously updated in response to changes to the active set of network validators. In order to avoid the issue of verifying changes to network validators, the active network validators influence the result of the derived random value during the decryption and verification process of the random number. Thus, changes to the active validator set produce a different random number, thereby rendering attacks using modified active validator sets ineffective. In addition, attempts to fork the chain and decrypt the number using a different active validator set are also rendered ineffective.
If a harmful actor were to attempt to fork the chain to predict the next number, the attack is rendered ineffective because the next number is generated randomly by the block proposer's enclave and cannot be predicted. Furthermore, a validator cannot execute blocks without the ability to decrypt the random number, which requires submitting prevotes to do so. If the malicious validator attempts to reuse a previous random number, the validator must submit a proof that links the encrypted random number with the block height and validator set, which it cannot.
To avoid being able to replay previous signatures with a new encrypted value, the encrypted number must be part of the transactions signed by the validators when creating their approval votes. To validate the prevotes, the TEE needs to receive the signatures and the original transactions. As an optimization, validation of the prevotes by Tendermint can be offloaded to the TEE to avoid verifying the same data twice.
Given that the voting power of validators fluctuates constantly and the computation of the active set's hash can be demanding, Secret Network achieves optimization by setting a minimum sensitivity to the changes in voting power. However, this can lead to disparities between the pre-vote weight and the weight calculated by the TEE, so the validation threshold must be lowered for the TEE. For instance, on Secret Network, if a 0.1% sensitivity to voting power changes is established, a prevote threshold of 61% would be necessary to consider the worst-case scenario where 50 validators, who together make up 66% of voting power, submitted prevotes with a 0.1%-ε higher voting power than what the TEE is aware of.
Finally, if a malicious actor were to somehow break Secret Network’s SGX and steal the REK it would be inconsequential because random numbers are generated independently for each block, so knowledge of a single random value is of no use without the ability to influence block creation. Even in a scenario where the malicious actor was a validator and could submit random numbers for blocks they propose, it would be ineffective because they do not have access to the consensus seed as stated above.
In conclusion, the Secret Network Random Number Generator (RNG) is a significant breakthrough in the generation of verifiable and fair random numbers on-chain. By leveraging the power of SGX and Tendermint consensus, Secret Network has created a first-in-class on-chain solution that generates random numbers with sufficient entropy, and these numbers are then encrypted, paired with an Initial Randomness Seed, and attached to block proposals where they are decrypted in order to generate true on-chain randomness. Coupled with cross-chain interoperable smart contracts over IBC, Secret Network enables thousands of developers and projects across the Cosmos access to state-of-the-art on-chain RNG.
A technical deep-dive into how Secret can provide confidential computation to dApps on other blockchains by leveraging cross-chain communication and Secret contracts.
This section will go over 4 different design options with highlighted use cases. This section should hopefully spark interest into using Secret's Confidential Computing Layer (or "CCL") as a whole and help developers understand the possibilities and limitations of this contract development paradigm.
The CCL in its most basic form is the idea that one can create products with privacy or additional utility for users who are not on Secret Network itself while using Secret contracts in the backend for the application.
Do you want to tap into Ethereum's liquidity, the polygon NFT scene or just want to add additional encryption-powered use cases to your current techstack? Secret's CCL might be a perfect fit.
The below design paradigms are separated based on the data flow in the app. The simplest apps only take data to and from Secret, while more complex apps either keep all the data separate, or take private data in and return public output.
The first design pathway only takes data from a Secret contract but is non-interactive beyond that. This means implementing use cases in this path can be considered relatively simple by leveraging IBC, Axelar GMP or other cross-chain messaging solutions.
In this dApp design scheme, we see Secret as a blackbox that has data accessible useful to any other chain on request. The requested data will have to be transferred publicly. The best example is on-chain randomness via .
By leveraging a slightly altered version of Tendermint and the Secure Enclave of every validator, Secret managed to create a truly random number generator on-chain. Every block and every contract call, the number is different and the numbers are verifiable via a callback after the block finalises. A developer on another blockchain can design a lottery completely on-chain by determining the random outcome using Secret-vrf. Simply make a contract on the consumer chain that requests a random-number (over IBC) from a Secret contract. Make sure the lottery entries close before requesting the number as it will arrive over public channels after the block closes on Secret. The number is now used within the contract on the consumer chain to assign the winner; no data has to return to the Secret contract.
Want to leverage Secret-VRF for your application on EVM or IBC? -
For the second design pathway, data flows towards Secret in an encrypted format with the output merely verifying if the input matches a pre-set pattern or any other defined criteria. Secret contracts here are mainly a decentralized and immutable data-storage mechanism with easily definable access controls.
The best example for this design pathway is web2-esque authentication for application login or other access controlled services. The first dApp leveraging this to its full extent was ALTER, a p2p encrypted communication platform enhanced with Secret smart contracts. The ALTER dApp allows users to store their session encryption key on the Secret Network inside of a Secret contract. Access to this contract and the key is then gated by a Wallet login functionality that ties one's crypto wallet public key to this contract - users can now sign in with their wallet and start messaging. However, this could be taken much further into the web2 realm by allowing sign in via Google web-Auth, password or even biometrics. You can also have usecases where we merely check if a user is who they say they are against identity, credit scoring or other personal information oracles.
The Authentication use case however can be taken much further. A Secret contract could not only check the input against a string it contains, but also influence actions on another chain. The first use case in this region is Unstoppable wallets, a form of on-chain Threshold wallets with unparalleled access control and ownership designed by SCRT labs. For unstoppable wallets, a Secret contract governs one part of a larger private key (MPC) and by programming access control policies into the contract, it will decide to sign or not sign a specific message. Every wallet signer commits the signature for the proposed TX to the Secret contract, the contract checks if the set policies are met and returns a valid signature that can be posted to Ethereum or any other blockchain for that matter. One could even decide to provide the Secret contract with a majority share of the private key allowing it to process transactions on its own for automated strategies or even Walletless applications where users can sign TXs with web2 authentication methods instead.
Want to learn more about Unstoppable wallets? - Watch from the SCRT labs CEO and Secret Network founder Guy Zyskind at Gateway to Cosmos Prague 2023.
In a "sister contract" design scheme the Secret contract and the CCL consumer chain act as two separate entities with which the user interacts separately. This allows developers to work with encrypted output, something none of the other CCL design flows allow.
Sister Contracts is the idea that one can accompany applications on other networks with access controlled data or functions by leveraging a Secret contract. The simplest usecase to explain here is in the realm of NFTs, certificates or other non-fungible personally assigned data objects. It is of paramount importance to many dApps that their usecase is accessible by many, hence why developers often choose for EVM or specifically Ethereum as their home network. With Sister contracts, one can have NFTs, certificates, Invoices or any other item be created on this user-rich chain while simultaneously linking it to a counterpart on Secret network. At any moment in time, the user who owns the item on Ethereum can verify their ownership to the Secret counter-party contract to reveal additional information only visible to them.
The above scenario requires users to interact with two networks to get the full experience of the respective dApp even if cross-chain messaging is already used by the developer. This is because private metadata can only remain private if the user interacts directly with the Secret blockchain without intermediaries like public bridging solutions. Developers can abstract many of these complexities by making intuitive UIs that allow users to use the same wallet for both networks (Metamask for ex.).
Do you want to flare up your NFT with additional private metadata, have access-controlled content for your marketplace or content platform, or store sensitive information related to invoices, IDS or certificates all while keeping your assets or dApps in the most liquid markets? - Then start building a Sister contract with Data privacy for your application now!
Lastly, a hybrid approach is possible between the previous design flows, one where there is a private input from any other chain but what is returned is a delayed public output so that all interactions for the user remain to a single chain. As explained before, we can only guarantee output privacy for a user if they are directly interacting with Secret network, but sometimes privacy is not needed for the output - it is only needed for the input.
One such example is private voting. Voting is inherently a public system, the result matters to everyone who voted and maybe beyond that. However, it is important participants can vote at their discretion with full privacy so there is an honest and non-biased outcome. In blockchain, this is not the case: close to all DAOs and chain governance is public by default.
One can design a dApp that allows Ethereum (or any other chain) DAOs to vote privately by voting via a Secret contract instead. Users encrypt their vote and their VP with the TX encryption key of the Secret network contract. The contract remains open for a set amount of time to collect votes after which it tallies results and emits the output. The output can be sent back to the consumer chain to complete the official voting process.
Although query
cannot change the contract's state and the attacker cannot decrypt the query output, the attacker might be able to deduce private information by monitoring output sizes at different times. See to learn more about this kind of attack and how to mitigate it.
For documentation on using Secret VRF in a native Secret contract, .
This same concept applies to many different use cases like sealed bid auctions and obscuring the game-state of turn-based games. The first mainnet application on Polygon called is using Secret Network in this way to enable a gamified auction platform. To enable these use cases, cross-chain messaging is needed from either Axelar GMP, IBC or other solutions. Bidshop will be allowing integrations for other dApps on their novel trustless message bridge as well, want to get connected? Let us know!
Want to get started with CCL or explore the possibilities but need some extra help?
Storage
No
Can be rolled back - merkle proofs will make it trusted (end 2023)
Env
yes
Protected against fork attack
Sender
yes
verified - fork protected
Inputs from sender
yes
verified - fork protected
Funds (input from non-compute module)
no
Fork attack can replay failed Tx from mainnet
Contract callback input
yes
anything that originates from within the enclave is trusted
Secret contracts are an awesome tool for native account abstraction. This all comes down to the fact that Secret-contracts can store (parts of) private keys in their state without revealing them to other network participants or observers. This concept was first thought out by scrt labs in a proof of concept multiple years ago [Code], then uttilised by Alter for easy log-in (as explained here), but the newest version uses novel additive HE and MPC to create a form of Unstoppable Threshold wallets [Code]. This proof of concept is now taken by obi.money and primevault to develop walletless experiences and secure threshold wallets for the next million users.
Obi.money can help you setup smart accounts for your Secret dApp already, feel free to reach out to them!
Want to learn more about Unstoppable wallets? - Watch this talk from the SCRT labs CEO and Secret Network founder Guy Zyskind at Gateway to Cosmos Prague 2023.
One can also use the native AuthZ module supported on Secret to have other accounts perform any specified action for any specified amount of time and limit. A usecase for this is more secure voting and autocompounding interfaces like Restake.app.
Smart contracts can also use ICA/ICS-999 (although host module is not supported yet) to control accounts on other chains over IBC and manage funds in interesting ways. Feel free to check out the codebases/Apps of Quasar and calc.finance to get an idea of what that can look like.
One can also leverage cosmos wide forms of account abstraction like this one implemented by Larry from delphi.
Secret network has active integrations with:
Using IBC Hooks, a contract on Ethereum can call a contract on Secret and get a response back.
Axelar Satellite/Squid Router
Using the Axelar SDK users can bridge Ethereum and other EVM/Non-EVM assets into Cosmos and Secret arriving immediately as Wrapped SNIP-20 private tokens into their Secret account.
Squid is a cheaper (gas wise) version for Axelar based bridging one can use as a widget.
Uses Secret's confidential computation to open a connection to any EVM chain - find examples under the EVM developer toolkit.
Coming soon:
To access all of our current token bridging interfaces, see the Secret Dashboard bridge page.
Query permits are an alternative querying method introduced in the SNIP-24 design specification. Query permits use of a cryptographic technique known as public-key encryption coupled with digital signatures.
A permit is a formatted message, it outlines several arguments such as what tokens the permit applies to and what permissions the permit should allow (e.g. should the permit allow the querier to view a user’s transaction history, balance, etc.). Permits are not saved in the smart contract state and do not require the initiation of a blockchain transaction. Therefore, permits are a less permanent way of gaining viewing access with less network strain.
Users can sign permits with their account’s private key to give certain dApps or parties viewing access to specific parts of their private data for a specified amount of time. To get viewing access a user sends a query, with the signed permit as an argument, to a smart contract. Once received, the smart contract, using the user’s public key, can validate the identity based on the signature the user provided. If the user’s identity is confirmed, the smart contract returns the data as requested.
For more information on Permits check out the permissioned viewing section under development or the SNIP-24 specification
Secret network has active integrations with:
Axelar Satellite/app.squidrouter.com
Using the Axelar SDK users can bridge Ethereum and other EVM/Non-EVM assets into Cosmos and Secret arriving immediately as Wrapped SNIP-20 private tokens into their Secret account.
Squid is a cheaper (gas wise) version for Axelar based bridging one can use as a widget.
Houdiniswap/Xblock
One can uttilise the Xblock integration or Houdini widget to let users onboard Crypto assets like XMR, BTC, ETH and more privately directly into SCRT
Kado.money
Kado supports SCRT and direct Noble USDC onboarding onto Secret network. One can uttilise their Widget to allow for in-app onboarding via Paypal, Credit, Debit and more.
TransAk
TransAk support SCRT onboarding using Debit, Credit, paypal and other gateways
Deciding who can and can't see your private data, and how to do it.
Permissioned viewing is entirely unique to Secret Network with its encrypted state. It gives you ability to have precise control over who has access to what data. In order to determine when data is allowed to be released however, you need to have a way to prove who is trying to access it. Our two ways of doing this are through Viewing Keys and Permits/Certs.
With the new Shockwave alpha update, Permits have become more efficient in almost every application. The exception is inter-contract communication permissions. Here, viewing keys still hold a lead. However, in some cases you may want to include an option to use both in your smart contract and leave the decision up to the users. Some users will have a preference for one over the other.
In distributed systems, replication of information on all machines is fundamental. In our case, Tendermint is responsible for replicating securely and consistently the state-machine amongst the nodes of the system.
It is composed of a Byzantine-Fault-Tolerance (BFT) Proof-of-Stake (PoS) consensus algorithm and a peer-to-peer networking protocol. It communicates to the application layer through the ABCI protocol.
Tendermint allows developers to focus on the application level and not take care of peer discovery, block propagation, consensus, etc …
In short, BFT represents the ability of a system to continue operating even if some of its nodes fail or act maliciously. In the Tendermint case, it can only tolerate up to 1/3 of failures, meaning that the blockchain will halt momentarily until 2/3 of the validator set comes to consensus.
Unlike Nakamoto consensus where it is subject to 51% attack (meaning that 51% of the actors acting maliciously could attack and alter the blockchain), Tendermint is more resistant as it is subject to a 66% attack.
Tendermint consensus module relies on Proof-of-stake and BFT.
Let’s take a look at the consensus process from a high-level standpoint:
Transactions are received by the node and go into a local cache mempool
Before going into the node mempool, Tendermint asks the application if the transaction is valid through “CheckTx” ABCI message
If it’s valid, transaction is added to the mempool and broadcasted to the peer nodes
A new consensus round is initiated with a Proposer Node
The Proposer selects transactions in the mempool to be included in a new block
This proposed block is broadcasted to all nodes (Pre-vote phase) and nodes verify that the block is valid, simultaneously verifying that the Proposer is also valid and sign the pre-vote message
If >2/3 of nodes pre-vote for this block, block is valid (but not committed) and we enter the pre-commit phase
Same as pre-vote, Tendermint will wait for >2/3 of nodes to sign the pre-commit message
There are two stages of voting to tolerate Byzantine faults, meaning that the pre-commit ensures that enough validators witnessed the result of pre-vote stage
The block is then committed, broadcasted to all nodes and transactions in this block executed by the application to update its state (for example account balance update etc)
Once a block is committed, there is no way to revert it and that gives an instant finality
All nodes are processing the transactions of every block even if they are not the block proposer
A new round is proposed and a new proposer is designated
💡 For a graphic view of this consensus process check: What is Tendermint?
This consensus ensures that all nodes maintain the same blockchain, i.e. the same list of blocks containing the past transactions and that all nodes could propose a block through Proposer rotation.
Users, known as delegators here, can choose which validator they want to delegate based on their reputation, stability, security and infrastructure. The amount of the native chain token owned and delegated to a validator represents its voting power, giving them the opportunity to be a new block proposer more often. Decentralization here is “measured” by the voting power distribution amongst the validators and not the number of validators.
ABCI layer is the communication protocol for Tendermint to make requests to the Application, like CheckTx (as we saw in the consensus explanation), indicate a Begin or End of a block, Deliver transactions to the application through DeliverTx, Query the application for account balance for example.
Finally, below a figure representing the Tendermint stack and all elements we went through in this paragraph:
The following process is based on how Tendermint works to reach consensus in a standard app chain but with specificity related to the privacy features of Secret Network. Please note that Secret Network is permissionless, validators and nodes can join the network at any time.
Developers write and deploy Secret Contracts which are executed by the validators, the binary for these contracts is public.
Users submit transactions to the mempool — which can include encrypted data inputs
Validators receive encrypted data from users and execute the Secret Contract
During Secret Contract execution:
Encrypted inputs are decrypted inside a Trusted Execution Environment
Requested functions are executed inside a Trusted Execution Environment
Read/write state from Tendermint can be performed (state is always encrypted when at rest, and is only decrypted within the Trusted Execution Environment)
Outputs are encrypted
The block-proposing validator proposes a block containing the encrypted outputs and updated encrypted state
At least 2/3 of participating validators achieve consensus on the encrypted output and state following Tendermint BFT
The encrypted output and state is committed on-chain to the specific contract
Using the Eliptic-Curve Diffie Hellman key exchange (ECDH) the user generates a shared secret from their private key and the Secret Network public key. The network can generate the same shared secret using the user public key and the Network private key (only available within the Trusted Execution Environment (TEE)).
The user then generates a shared tx_encryption_key
using HKDF-SHA256 and the shared secret generated in step 1. The pseudo-random HDKF is used to ensure deterministic consensus across all nodes. The random component comes from a 256-bit nonce so that each transaction has its own encryption key, an AES-256-GCM encryption key is never used twice.
After initiating a transaction the user encrypts the input data with the shared transaction encryption key, using an AES-256-GCM authenticated encryption scheme.
The user then sends a single transaction with encrypted input data, gas fee in SCRT, and optionally a SCRT deposit (as required by the contract) to the contract address. The transaction adds a message with the user public key and the used Nonce so that the network can generate the same tx_encryption_key.
Validators receive the transaction in the shared mempool, after the Tendermint check_tx
is deemed successful, and take up the data to process the transaction.
Each validator runs on a machine with an Intel SGX compatible CPU. The enclave within this CPU is the trusted component, whereas the process interacting with the enclave locally from the outside is the untrusted component. The encrypted transaction input is passed from the untrusted to the trusted component.
The enclave uses ECDH to derive the same shared secret from the users public key and the networks private key. The network then derives the tx_encryption_key
from the public signed nonce and this shared secret using HDKF. Within the trusted component the transaction input is decrypted to plaintext.
The requested contract computation is executed on the plaintext input by the WASMI runtime, which is instantiated within the trusted enclave.
There are several possible outcomes from each computation, all of which occur on-chain simultaneously:
The contract state is updated.
The transaction output is encrypted for the transaction sender and committed on-chain
If present, callbacks to other contracts.
If present, send messages to other modules.
The output of the computation is encrypted using the tx_encryption_key
and an AES-256-GCM authenticated encryption scheme.
The encrypted output is then returned to the untrusted component of the validator node.
The validator that proposes the current block broadcasts the encrypted output.
Other validators compare their result to that of the block proposer. If more than two-thirds of the current voting power agrees on the result, the proposed block (and all transactions within it) are included in the Secret Network.
Want to learn about Secret transactions in a video format instead? Check out the last part of the Secret Network techstack video series.
Step-by-step guide on how to execute Secret Network smart contracts that communicate with each other
In previous sections, we explored the creation of isolated smart contracts within the Secret Network ecosystem. To construct more intricate systems using smart contracts, it is essential to establish effective communication between them. This documentation will delve into the implementation of secure and efficient communication between secret contracts, allowing you to build powerful, interconnected applications on Secret Network.
In this tutorial, we will be designing a simple smart contract that can execute a counter smart contract that is already deployed to the Secret Testnet. Thus, we are working with two smart contracts:
Manager Contract - which executes the Counter Contract
Counter Contract - which is executed by the Manager Contract
By the end of this tutorial, you will understand how to implement the Increment()
function on a Manager smart contract, which increments a counter smart contract by one every time the Increment function is called.
To follow along with this tutorial step-by-step, clone down the Secret Network counter template repo here 😊
We will be designing a Manager Smart Contract which can execute a Counter Contract that is deployed to the Secret testnet. Let's start by creating the message that we want to execute on the counter smart contract. In the src directory (which currently contains contract.rs
, lib.rs
, msg.rs
and state.rs
), create a counter.rs
file and add the following:
CounterExecuteMsg
contains a single function Increment{}.
This is the function we will call to increment the counter contract once we have completed designing our Manager smart contract.
Now, navigate to the msg.rs
file. Replace the existing code with the following:
Here we have an empty InstantiateMsg
, as well as an enum ExecuteMsg
, with a single variant, IncrementCounter
, which contains two strings: contract
and code_hash.
This is the contract address and code hash of the counter contract, which we will be calling from the Manager contract.
What is the code hash?
Unlike other Cosmos chains, Secret Network requires the hash of the smart contract in addition to the address when executing calls to smart contracts.
Contract hashes are what binds a transaction to the specific contract code being called. Otherwise, it would be possible to perform a replay attack in a forked chain with a modified contract that decrypts and prints the input message.
Now, navigate to the contract.rs
file. Replace the execute entry point with the following:
When the IncrementCounter
variant is matched, the function calls the try_increment
function, which contains two fields, contract
and code_hash
. The contract
field is the address of the contract to be incremented, and the code_hash
field is the code hash of the contract. This function contains the logic for incrementing the counter in our counter contract.
Remember the counter.rs file that we created earlier with the CounterExecuteMsg
enum containing Increment{}
? 🤔 Now, in the try_increment()
function, we are going to use a WasmMsg to call Increment{}
in order to increment the counter contract.
In CosmWasm, a WasmMsg
is an enum used to perform actions such as instantiating a new contract, executing a function on an existing contract, or sending tokens to a contract. Here are the main variants of WasmMsg
:
Instantiate
: This variant is used to create a new instance of a Wasm smart contract. It contains the following fields:
code_id
: The ID of the Wasm code to instantiate.
msg
: The message to initialize the contract (usually a JSON object).
funds
: The amount of tokens to send to the contract upon instantiation.
label
: A human-readable label for the new contract instance.
admin
: An optional address that, if provided, will have administrative privileges over the contract instance (e.g., for migration or update purposes).
Execute
: This variant is used to execute a function on an existing Wasm smart contract (and is the variant that we are calling in our contract) It contains the following fields:
contract_addr
: The address of the contract to execute the function on.
msg
: The message to be processed by the contract (usually a JSON object).
funds
: The amount of tokens to send to the contract along with the execution message.
Migrate
: This variant is used to migrate an existing Wasm smart contract to a new code version. It contains the following fields:
contract_addr
: The address of the contract to migrate.
new_code_id
: The ID of the new Wasm code to migrate the contract to.
msg
: The message to initialize the new code version (usually a JSON object).
In the counter.rs file, comment out or delete the existing execute functions and add the following:
The try_increment
function creates a WasmMsg::Execute
message to increment the counter in the specified smart contract and returns a Response
object containing the message and an attribute. When the Response
object is returned by the smart contract's execute
function, the blockchain will execute the WasmMsg::Execute
message, effectively incrementing the counter in the target contract.
Here's a breakdown of the code inside the function:
let exec_msg = CounterExecuteMsg::Increment {};
: This line creates a new CounterExecuteMsg
enum instance with the Increment
variant, which represents a message to increment the counter.
let cosmos_msg = WasmMsg::Execute { ... };
: This block creates a WasmMsg
enum instance with the Execute
variant. The Execute
variant is used to execute our Counter smart contract. The contract_addr
field is set to the contract
parameter, the code_hash
field is set to the code_hash
parameter, the msg
field is set to the binary representation of exec_msg
, and the funds
field is set to an empty vector, indicating no funds are sent with this message.
Ok(Response::new() ... )
: This block constructs a new Response
object with the following:
.add_message(cosmos_msg)
: The cosmos_msg
(a WasmMsg::Execute
instance) is added to the response as a message to be executed after the current contract execution finishes.
.add_attribute("action", "increment")
: An attribute with key "action" and value "increment" is added to the response. Attributes are used to store additional information about the operation and can be useful for indexing and querying transactions.
You can view the completed code repo here which contains the upload, instantiation, and execution functions using secret.js.
Let's focus on how to write the increment function as seen below:
The secret.js function increase_count
sends an increment_counter
message (which is the message we defined in msg.rs
) to our Counter smart contract. This message increments the counter contract via the WasmMsg that we defined in the increase_count
function.
You can call this function by commenting out the other functions in index.js and running node index.js
in the terminal (make sure you are in the secret-messages/manager/node
directory).
Now, in your terminal, navigate to the secret/counter/node
directory. Call the query_count
function to see the current count of the counter contract. After using the manager contract to increment, it should now be incremented by one!
Congratulations, you have now created a Manager smart contract that interacts with a Counter smart contract on Secret Network! By designing a Multi-Contract System, you can enable secure and efficient communication between secret contracts, allowing for more intricate and interconnected decentralized applications.
Secret supports IBC-Go v4 and several additional tools and middleware. ICA is supported but functions are not yet enabled nor is the host module live, if you need this please reach out to the Devrel team via discord or Telegram. Tools supported:
IBC hooks by Osmosis Labs - Secret - v1.11
WASM hooks: allows ICS-20 token transfers to initiate contract calls, serving various use cases.
Example: Sending tokens to Secret and immediately wrapping them as SNIP-20 token. For example, ATOM on Hub -> ATOM on Secret -> sATOMS on Secret
(2 transactions on 2 chains) now becomes ATOM on Hub -> sATOM on Secret
(1 transaction).
Example: Cross-chain swaps. Using IBC Hooks, an AMM on Secret can atomically swap tokens that originated on a different chain and are headed to Secret. The AMM can also send those tokens back to the originating chain.
Axelar GMP: Using IBC Hooks, a contract on Ethereum can call a contract on Secret and get a response back.
Ack callbacks: allow non-IBC contracts that send an IbcMsg::Transfer
to listen for the ack/timeout of the token transfer. This allows these contracts to definitively know whether the transfer was successful or not and act accordingly (refund if failed, continue if succeeded). See usage example here.
Packet forward middleware (PMF) by Strangelove - Secret v1.9
Other chains are able to more easily route SCRT in the interchain. For example, sending SCRT from Osmosis to Hub now becomes a single transaction from Osmosis -> Secret
rather than a transaction from Osmosis -> Secret
, then a transaction from Secret -> Hub
.
As of Secret Network V1.7 delegators can autocompound their SCRT Staking rewards using the native Restake feature.
Normally SCRT delegated to a validator accrues SCRT rewards every block as a claimable balance. With the autorestaking feature enabled however the SCRT rewards will never be claimable and also never in the claimed balance state.
Every x amount of blocks (currently 1000 set by the chain parameter - restake_period
) all claimable rewards, on the Restake enabled validators by the user, will be removed from the reward balance and accredited to the staked balance.
The code implementation for native Restake is available
Delegators can enable Restaking independently for every validator they stake with using the set-auto-restaking
command available in or on any UI implementing the Secret.JS command.
The feature only activates if the delegator has a balance with the validators larger than minimum_restake_threshold
which is currently set to 10 SCRT
Start autocompouding your rewards now at the !
Secret Network uses Intel's Software Guard Extensions (SGX) implementation of TEE technology. TEE refers to a secure area of a processor where data is inaccessible to any other component in the system. A TEE acts as a blackbox for computation, input and output can be known, but the state inside the TEE is never revealed.
Intel’s Software Guard Extensions (SGX) is a set of security-related instructions built into certain Intel CPUs enabling TEEs. By using SGX chips, the chip owners, system operators, and observers have strong cryptographic guarantees that no party can view what's happening inside of the Secret memory space.
This part of the documentation will discuss all aspects of TEE technology and the way that Secret Network implements it for a secure private computation environment.
Introduction to Secret Network viewing keys with code examples
Viewing keys are passwords meant to validate users at times when the blockchain cannot. Specifically in queries, the query sender isn't authenticated and the contract doesn't know who is the querier. Therefore viewing keys were invented to provide a way of access control for users:
Alice sends a transaction set_viewing_key(password)
The contract stores (alice,password)
Later on, a query is sent to the contract query("balance",alice,password)
If (alice,password)
matches what's in storage, the contract returns Alice's balance to the querier.
To see an implementation of viewing keys in a Secret smart contract, check out the
Secret Network developed the for creating and managing viewing keys in Secret smart contracts. The methods provided include:
set_seed
: Sets an initial pseudorandom number generator (PRNG) seed for the store.
create
: Creates a new viewing key, saves it to the storage, and returns it.
set
: Sets a new viewing key based on a predetermined value.
check
: Checks if a viewing key matches an account.
To make use of the secret-toolkit viewing keys package, import it into your cargo.toml
file:
This contract is designed to create top secret messages that can only be decrypted if the user has the correct viewing key that is associated with the secret_message
struct.
This function stores a secret message at a specified index in the contract's storage, which is mapped to a viewing key. This ensures that the secret message can only be accessed by the correct viewing key, and that each secret message has its own unique viewing key.
Let's go over it in more detail:
A new viewing key is created by calling ViewingKey::create
. The parameters passed include the mutable dependencies (which includes the storage), the environment env
, the MessageInfo
, the sender account, and a hard-coded entropy value b"entropy"
.
The secret message is then stored in the contract's storage and is mapped to the viewing key. The SECRET_MESSAGE.insert
line is performing this task.
Next, the viewing key itself is stored in the contract's storage, but this time indexed with a user-defined index
parameter and prefixed by the sender's account address. This is done by VIEWING_KEY.add_suffix(info.sender.as_bytes()).insert(deps.storage, &index, &viewing_key)
.
Finally, the function returns a default Response
instance indicating successful execution of the function.
Please follow the guide
An introduction to Secret VRF, a secure and verifiable random number generator
Secret Network's randomness API allows developers to access random numbers in their CosmWasm contracts, enhancing the capabilities of the platform. The randomness feature is accessible within Secret Contracts through the Env struct. It includes an optional random field, which contains a random number as a Binary type. The random field is only available when the "random" feature is enabled.
Randomness is essential in many applications, including:
Gaming and gambling platforms, where fair and unpredictable outcomes are crucial
Cryptographic systems that require secure random keys or nonces
Randomized algorithms for various use cases, such as distributed systems or optimization problems
The proposer for each block generates a strong, random seed inside .
This seed is then included in the block header and signed by all validators who can verify its authenticity inside their SGX.
Secret Network's in-SGX light client prevents the proposer from simulating a block before all other validators sign it. Consequently, the proposer cannot gain maximal extractable value (MEV) by generating random seeds until they find a favorable simulation of the block.
Before calling the contract, the chain injects env.block.random = hkdf_sha256(block_random_seed + wasm_call_count)
.
Thus each contract call gets a unique random seed.
For a more in-depth explanation of why and how this method of randomness works feel free to read the
A step-by-step tutorial of how to use Secret Network's randomness API to generate a coin flip
In this tutorial, you will learn how to access the randomness feature and use it with a smart contract that returns a truly random coin flip 🎉
For a detailed feature explainer head to the
In your Cargo.toml file, add secret-toolkit-storage 0.10.1:
What follows is a step-by-step tutorial of how to use Secret Network's randomness API to generate a coin flip (returning either 0 or 1) with true randomness. You can follow along and/or view the .
To consume the random number, you need to import the necessary dependencies in your contract.rs
file in order to access the random number from the env parameter.
In your contract, import the necessary dependencies (these are already imported in the cloned repo):
In the contract's entry point (e.g., execute, instantiate, or query), you can access the random number from the env
parameter:
The env and block_info structures are defined as:
Where random
is 32 bytes and base64 encoded.
Below is a simple coin flip function that uses the randomness feature:
try_flip()
uses the config
function to update the state of the smart contract by flipping a coin and storing the result in the flip
field in the state
variable. Specifically, it generates a random number using the random
field of the env.block
object, which is an optional value representing the most recent block's metadata, and takes the modulo 2 to obtain a value of either 0 or 1. It then updates the flip
field of the state
variable to this value.
Now, let's compile, upload, instantiate, and execute the contract to see it in action!
To compile your contract, run make build-mainnet-reproducible
This returns the optimized contract wasm file, ie contract.wasm.gz
Now that you have a contract address you can execute the coin flip with the randomness feature!
You might have to execute the flip function a few times to see the queried flip change, since there is a 50% chance the flip will return the same number :D
Permits are the successor to viewing keys. With increases in efficiency made in the Shockwave update they are now the defacto viewing permission method in almost every situation.
Permits are simply a dummy transaction signed by a wallet. The wallet is able to use this signature as proof that the transactor is who they say they are. Any wallet capable of using Tendermint is able to carry out this process.
For an in-depth implementation of query permits, navigate to the .
Generating a permit happens outside of the smart-contract, but checking them in your smart contract is extremely simple. Once again the Secret-Toolkit provides a package for us, aptly named permit
. The function that checks the permit is called validate
shown below.
To use this, simply call validate
inside of your query provided with the necessary permit variables.
Permits can be used at any time, and are amazing for almost every permissioned viewing application. The only time it may be more efficient to use viewing keys instead is during inter-contract communication.
If you would like to see an example implementation of Secret Network viewing keys, see the .
Let's review the function:
Viewing keys provide a mechanism for access control in blockchain applications when the blockchain itself can't authenticate the query sender. Secret-toolkit
allows developers to set seeds, create and check viewing keys, and set a new viewing key based on a predetermined value
Should you have further questions, please reach out on and a Secret developer will get back to you shortly
Upload and instantiate your contract to Secret Network testnet with the upload script .
If you would like to use your own wallet addres, be sure to update the .
To flip the coin, update the and with your parameters and run:
And to query that it was successful, update the and with your parameters and run:
Congrats! In this step-by-step tutorial on creating a coin flip contract, you learned how to compile, upload, instantiate, and execute a contract on Secret testnet using Secret Network's randomness API to generate random numbers 🎉 For documentation on Secret VRF in a contract on another IBC-connected chain, .
Optionally perform code and state migrations of Secret Network smart contracts
In some cases, for example when wanting to deliver support to users and deploy bug fixes, it makes sense to have the ability to change the code. This is especially true for services which wish to change algorithms, add features, etc. For these use cases, contracts can be written to allow various modes of migration. Doing so means that anyone inspecting the source code will be able to know that such a migration is possible and will require that users have a higher level of trust in the contract administrators, as they can effectively install a backdoor in their product.
Secret used to not support the native CosmWasm implementation of contract migratability, since v1.11 it does. The old manual examples are still displayed here as a reference.
During the v1.11 upgrade a set of old contracts were allowed to become migrateable, these contracts behave slightly different in that they are callable by both their new and old code hash. For more information about the transition period please read this forum post!
Factory contracts are contracts that create other contracts. They are useful for:
Dividing your app into parts, each with different responsibilities.
Extending the scope of your app by dynamically adding a piece of functionality.
Managing different parts of the app from a single location, etc.
The Factory Contract Template is a good starting point for setting up your own factory contracts and experimenting with them.
Here are some examples of active projects that use factory contracts:
SecretSwap, where a Factory contract creates a new Child contract for every new pair supported on the exchange. (Cosmwasm v0.10).
SecretJack, where a Parent "Bank" Contract which manages the funds creates a single "Game" Contract which manages the game logic. (Cosmwasm v0.10).
After the child contract is stored on chain, you can instantiate child contracts from the factory in the following manner:
Assume the child contract expects the following instantiate message:
To instantiate the child contract, you can send a submessage from the parent with the child's instantiate message:
If you don't care about the reply, you can send a regular message instead:
Assume the child contract expects the following instantiate message:
To instantiate the child contract, send a message on the parent's response:
get_contract_code_hash helper function
To retrieve a Secret Network smart contract's code hash from within a Secret contract call, you can use Stargate queries to directly query the on-chain data from inside the contract.
This helper function is particularly interesting if you desire to make complicated contract structures that involve submessages or just cross-contract messages to different Secret Network contracts. With this code snippet, you do not need to supply the code_hash
of each contract that you are going to call in submessage or normal messages. It is sufficient to know the contract address of the other contract, the code snipped will fetch the latest on chain code_hash
for you.
Be aware that contracts can be upgraded on Secret Network! Since this code snippet always fetch the code_hash
directly from the chain without any extra check (which was implictly done by manually supplying the code_hash
), you need to be careful about silently (perhaps maliciously) upgraded contracts which potentially releveal confidential information.
The Secret Network team has designed a helper function, get_contract_code_hash
, exactly for this purpose.
See an example usage of get_contract_code_hash here.
Simply add the Anybuf package and the "stargate"
feature for cosmwasm-std to your cargo.toml
:
And then add the function to your contract:
Learn how to use submessages on Secret Network
In the CosmWasm SDK, submessages are a powerful feature that allows a contract to execute additional messages within the context of its own execution. SubMsg
just wraps the CosmosMsg
, adding some info to it: the id
field, and reply_on
. reply_on
describes if the reply
message should be sent on processing success, on failure, or both. To create submessages, instead of setting all the fields separately, you would typically use helper constructors: SubMsg::reply_on_success
, SubMsg::reply_on_error
and SubMsg::reply_always
.
You can learn more about submessages here!
In this tutorial, you will learn how to use submessages to execute a counter smart contract on Secret Network from another Secret Network smart contract 😊
git clone
the submessages example repository:
In this tutorial, we will use submessages to execute a counter smart contract that is already deployed to the Secret Network Testnet. Thus, we are working with two smart contracts:
Manager Contract - which executes the Counter Contract using submessages
Counter Contract - which is executed by the Manager Contract
This tutorial follows the same design patterns of the Cross Contract Communication tutorial, but uses submessages in place of Wasm messages
We will be designing a Manager Smart Contract which can execute a Counter Contract that is deployed to the Secret testnet. Let's start by examining the message that we want to execute on the counter smart contract. In the src
directory, open msg.rs and review the Increment
msg:
Increment
contains one parameter, the string contract.
This is the contract address of the counter contract, which we will increment.
Where is the code hash?
Unlike other Cosmos chains, Secret Network requires the hash of the smart contract in addition to the address when executing calls to smart contracts.
Contract hashes are what binds a transaction to the specific contract code being called. Otherwise, it would be possible to perform a replay attack in a forked chain with a modified contract that decrypts and prints the input message.
However, there is no need to pass a code_hash
when doing cross contract or sub message calls because we have designed the helper functionget_contract_code_hash
which we call internally when executing the Increment
function.
Notice that we implement HandleCallBack for our ExecuteMsg
enum, which is what allows our submessages to be converted into a CosmosMsg
and executed:
Submessages offer different options for the other contract to provide a reply. There are four reply options you can choose:
In order to handle the reply from the other contract, the calling contract must implement a new entry point, called reply
:
Here is an example of how to handle the reply:
The submessage returns a Result
containing:
Ok(Response)
if the submessage executed successfully, with an action attribute set to "increment".
Err(ContractError)
if the submessage execution failed, with the error encapsulated in a custom contract error.
Now let's use this manager smart contract to increment a counter smart contract with submessages!
The counter contract we will be executing is deployed here:
Or deploy your own counter contract here :)
cd
into manager/node:
Install the dependencies:
Run node execute to execute the counter contract:
Upon successful execution, you will see a tx
returned in your terminal:
Now, let's query the counter contract to make sure it was executed correctly!
cd
into counter/node:
Install the dependencies:
And run node query
:
You will see that the counter contract has been incremented by 1 :)
In this tutorial, you learned how to utilize submessages in the CosmWasm SDK to perform complex, atomic operations within smart contracts on the Secret Network. This guide walked you through executing a counter smart contract from another contract, detailing the setup of a Manager Contract that triggered the Counter Contract using submessages, managing replies, and verifying the execution results 🤓
Introduction to writing tests for Secret Smart Contracts
Tests help ensure the quality, stability, and correctness of your Secret smart contracts. A single bug or vulnerability in a smart contract can lead to consequences such as the loss of funds or the compromise of sensitive information. By thoroughly testing Secret smart contracts with unit tests, developers can identify and address issues early in the development process, reducing the risk of errors and vulnerabilities for contracts that are deployed to Mainnet. Let's begin by learning about unit tests in Secret Contracts. 🎉
More info coming soon
Now that we have a better understanding of how Secret is leveraging SGX, let’s see how the TEE and enclaves works with the Trusted and Untrusted cores.
Responsible for running the Cosmos SDK and Tendermint.
Contains code for creating and managing the enclave (load and destroy).
Can call the CosmWasm module and kick off Secret Contract execution within the enclave.
Responsible for executing Secret Contracts.
Responsible for SGX-specific mechanisms: Remote Attestation and Sealing.
Able to make read/write calls from the Tendermint state at any point during execution.
The enclave only stores the seed. The enclave may also store the local node's key pair.
Note: During contract execution, only the state of the contract being executed can be changed. Other contracts can be queried (i.e. run code that can't change the state of another contract) synchronously, but calls to other contracts and requests for transactions can only be queued. Those operations will happen after the contract has finished running. This is intentional as it prevents a lot of bugs, like the re-entrancy bugs plaguing Ethereum.
Below is a diagram of how the Untrusted and Trusted behave on a User transaction and Secret Contract execution:
\
A description of building the same code for both vanilla CosmWasm and the Secret version.
The crosschain contract that we are examining can be cloned from . This repo is a demo of writing a simple voting contract that is intended to be deployed on multiple chains with different types of CosmWasm in a single codebase. This demo supports two types of CosmWasm: vanilla
(i.e. original, like Terra or Juno supported) and secret
(i.e. SecretNetwork supported).
The contract code uses conditional compilation and feature flags in Rust to support different configurations within a single codebase. If you navigate to , you will find two sets of imports for different features, either "secret"
or "vanilla"
. The imports are conditionally compiled using #[cfg(feature = "…")]
attributes. The "secret"
feature uses the secret_std
crate, whereas the "vanilla"
feature uses the cosmwasm_std
crate.
For more information about the feature differences of Standard CosmWasm vs the secret version please visit TLDR: No raw queries, iterators and upgradeability
Contract.rs defines several functions:
instantiate
: This function initializes the contract and sets the initial vote count for both options to 0. In this instance, it is the same for both Secret and vanilla CosmWasm.
execute
: This function processes the ExecuteMsg
, which supports voting. It calls the vote_impl
function to perform the vote.
vote_impl
: This function is implemented twice, once for each feature ("secret"
and "vanilla"
). It checks whether the voter has already voted, and if not, it updates the tally for the chosen option.
query
: This function processes QueryMsg
, which supports two queries: getting the vote tallies (Tally
) and getting a list of voters (Voters
). For the Tally
query, it returns the vote count for both options. For the Voters
query, it calls the voters_query_impl
function.
voters_query_impl
: This function is also implemented twice for each feature. It retrieves the voters list based on the provided page number.
Let's examine the differences in vote_impl
based on which type of CosmWasm we are using. The overall structure and logic of the function are the same, but there are differences in the specific methods called on the VOTERS
, OPTION_1
, and OPTION_2
objects. These differences arise from the different crates used for the "secret" and "vanilla" features.
In the "secret" version of vote_impl
:
VOTERS.contains(deps.storage, &info.sender)
is used to check if the voter already exists in the storage.
VOTERS.insert(deps.storage, &info.sender, &1)
is used to insert the voter into the storage.
In the "vanilla" version of vote_impl
:
VOTERS.has(deps.storage, info.sender.clone())
is used to check if the voter already exists in the storage.
VOTERS.save(deps.storage, info.sender, &1)
is used to save the voter into the storage.
The rest of the function, including the match statement for updating the vote tally, is the same between the two versions. Now let's examine the difference in querying functions between the two versions.
Vanilla CosmWasm Iterators are not supported on Secret Network because users cannot iterate over data stored by different users as there is no access to their dedicated encryption key. However, iteration is still possible on Secret Network through the use of the secret_toolkit
for storage in place of cosmwasm_std
.
In the "secret" version of voters_query_impl
:
The VOTERS.paging_keys(deps.storage, page, PAGE_SIZE as u32)?
method is used to retrieve a list of voters corresponding to the requested page. This method is specific to the secret_toolkit
storage API and directly handles pagination.
In the "vanilla" version of voters_query_impl
:
The VOTERS.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
method is used to retrieve an iterator over all the keys (voters) in the storage. Pagination is implemented manually in the function.
The main difference between the two implementations is that the Secret version relies on a secret_toolkit
pagination method (paging_keys
), whereas the Vanilla version manually implements pagination using iterator methods.
The contract uses state_secret
or state_vanilla
modules for managing the state, depending on the selected feature. The state management includes saving the vote counts for each option and managing the list of voters. Let's examine the differences between state_secret.rs
and state_vanilla.rs
.
In the Secret version:
The secret_std::Addr
is used as the address type.
The secret_toolkit::storage::Item
and secret_toolkit::storage::Keymap
types are used for storage management.
The storage objects are initialized with Keymap::new
and Item::new
methods, passing the byte representation of the corresponding prefixes.
In the Vanilla version:
The cosmwasm_std::Addr
is used as the address type.
The cw_storage_plus::Item
and cw_storage_plus::Map
types are used for storage management.
The storage objects are initialized with the Map::new
and Item::new
methods, passing the corresponding prefixes directly.
Thus, the Secret version relies on the secret_std
and secret_toolkit
crates, while the Vanilla version uses the cosmwasm_std
and cw_storage_plus
crates. However, the overall purpose of the state management objects (VOTERS, OPTION_1, and OPTION_2) remains the same in both versions.
Since the V1.11 Upgrade Secret now supports native migrateability for contracts
The Secret Network's version of the CosmWasm runtime disables the ability to natively migrate contracts. Data written to storage using some contract can only ever be accessed by that same contract. This is done for trust and security reasons. Once a user sends some information to a secret contract, there should not be an easy way for anyone to change the code that has access to this information. If it was possible, then the new code would be able to leak any information entrusted by the user to the old code.
That being said, contracts are fully programmable, and "not easy" is not impossible. If it was, we wouldn't even be here 😉. In some cases, for example when wanting to deliver support to users and deploy bug fixes, it makes sense to have the ability to change the code. This is especially true for services which wish to change algorithms, add features, etc. For these use cases, contracts can be written to allow various modes of migration. Doing so means that the anyone inspecting the source code will be able to know that such a migration is possible and will require that users have a higher level of trust in the contract administrators, as they effectively install a backdoor in their product.
The contracts in this repository show various migration strategies that can be used and adapted in real-world contract. These should not be considered production-ready examples, and some applications may have more specific or creative migration paths.
For examples of implementations of the techniques described here see:
Did you know? Contract migration/upgradability in a privacy-preserving way is on the roadmap for Secret Network
The idea of a handoff architecture is that the original deployed contract is written in a way that allows a contract admin to give a new contract permission to directly collect private information from it. In order to add a basic level of protection, the reference in this repository makes sure that the information can only be collected by a contract, and not just a regular user.
This is done by exchanging a secret with a contract through a callback. The admin would provide the first contract with the address and hash of the new contract. The first contract would then generate a secret based on some source entropy (internal or external) which it would send to the second contract (A malicious admin can still write a contract that would leek this secret, but we already decided we have to trust the admin). The second contract can then query the first contract, either once or with some paging, to retrieve and store all the previously hidden information. An important detail shown in the example is that the first contract essentially gets locked as soon as the migration starts. This is done in order to make sure the data stored in it doesn't change anymore. new interactions should be with the new contract, after the admin completes the migration.
In theory, one could write a contract that only forwards messages to another contract that is configured by the contract admin. Users would then only interact with the facade, rather than the implementation contracts. You can imagine that it would define a small, reserved interface of messages that do not get forwarded, and would only be seen by the facade. Messages such as ChangeOwner
, ChangeImplemntation
, etc. This would allow great flexibility for contract authors to just deploy a new version of a contract, and point the facade to the new implementation. Paired with the technique, you could have a complete change of implementation, with only a short downtime. (The facade contract can even be written in a way that informs users that the service is temporarily down, and that they should retry in a couple minutes)
Unfortunately, this is currently not feasible, at least not in a pretty way, because of limitations of the available json parsing libraries. The only one i'm aware of that can run on an environment that forbids floats is serde-json-wasm
, but it doesn't support parsing unstructured messages (like e.g., serde_json::Value
). A workaround then, is to include the message to the "backend" contract as a serialized JSON in a string field of the message to the facade. It's not as pretty but can be implemented without having to start rewriting ecosystem packages.
Another, less flexible alternative, is to define the full API that you will commit to for the rest of the application's lifetime. Then. you can just parse-reserialize messages that aren't meant for the facade itself.
This is more of a migration technique for products rather than specific contracts, but it's worth mentioning.
The idea is that if your application needs to deploy a new contract every time it serves customers (e.g., game rooms, bids, etc.), then you can write a factory contract that handles some things for you. The factory contract can be used for many things, from providing one authentication method for queries to multiple contracts, for aggregating usage statistics on-chain, for sharing information between contracts in a structured way, and more. Another use for it, is changing the implementation of new contract that will be deployed in the future.
You can think of the factory as a sort of facade, so it can also include a handle that would allow the admin to reconfigure the code-id, and maybe even init parameters, used to open new contracts (game rooms, bids, etc.).
A SNIP-20 contract can also implement the Receive
interface and allow users of one token to Send
a swap request to the new token. The new token will lock those funds in its custody for the rest of time, and mint new tokens, with some exchange rate, to the sender address. This technique will require zero preparation by the original token contract.
A similar technique can be used by applications with similar ideas to SNIP-20.
In case of emergencies, it can be useful to have certain admin functions to alter the functionality of the contract in some way that are only usable by the admin. For example, if your contract receives funds and forwards them to a different address, but you lose the keys to the to that address, you want to ensure their is a function implemented that will enable you to change it. Or possibly there may be some situation where you want to deactivate the contract (aside from the admin function to reactivate it).
It's important to consider what possibly go wrong with the contract and what can be done to fix it ahead of time, and implement those fixes. On the flip side, making too many functions alterable by the admin may make your dapp more centralized and users may become more cautious to use it. Balance these two sides carefully during dapp creation.
In some cases, you may want to write a new updated smart contract. This can be for multiple reasons, such as fixing bugs or adding new features. However, because you cannot just change the code of a contract already uploaded on the chain, the upgrade process becomes significantly more complex than web2 and there are many things you will need to consider.
Due to the expense of sending large amounts of data, it would be unreasonable or impossible to send all data to a new contract in a single transaction if you store a significant amount, such as in the case of a token contract which may have tens of thousands of users. You will have to set up functions to transfer data strategically and in manageable chunks.
If a single admin calls a function that would transfer over all user data to a new contract, in many cases that would also mean they could simply read the data themselves by transferring it to a contract they have total read access to. To prevent this risk to user privacy, it is wise to only give your users permission to transfer over their own data. Using the example of a token contract once again, allow only the users permission to transfer their token balance to the upgraded token contract. Otherwise there is a risk of an admin reading their balance without their permission.
In some cases, you may want to ensure once the data is transferred it is immediately deleted, or otherwise prevented from being accessed again from the old contract. This is to prevent the possibility of a user taking advantage of their data existing on two contracts simultaneously. For example, if an NFT contract was updated and the NFTs were moved over to the new contract, but the old contract did not remove them after transferring, you could risk your users selling both the old and new copy their NFTs. Your specific contract may not require data deletion like this, but it is important to consider all possibilities and think from the perspective of potential attackers to decide if this is a risk you need to prevent.
How to write Unit Tests for Secret Contracts
To ensure that Secret smart contract code is reliable and free of errors, it's important to test it thoroughly. One effective way to achieve this is through unit testing.
Unit testing involves breaking down a program into its smallest components or units and testing each one in isolation. By testing each unit separately, developers can pinpoint the root cause of any errors or bugs and fix them quickly.
In Rust, unit testing is supported by the , which provides a set of macros and utilities for writing and running tests. Rust's testing framework is built into the standard library and is designed to:
Set up any needed data or state.
Run the code you want to test.
Assert the results are what you expect.
Secret Contracts utilize the Rust testing framework as well as additional cosmwasm-std
utilities, such as and functions.
We will explore the basics of unit testing for Secret Contracts, including how to write and run tests, organize test code, and use test-driven development (TDD) to ensure code quality. With this foundation, you'll be well on your way to writing robust and reliable Secret contracts.
For a practical understanding of writing unit tests for Secret Contracts, navigate to the . Here you will find 3 unit tests:
proper_initialization()
increment()
reset()
Let's examine proper_initialization()
to understand how unit tests check for correctness of various execution messages in Secret Smart Contracts.
The use cosmwasm_std::testing::*;
line imports all the testing utilities from the cosmwasm_std
crate.
The #[cfg(test)]
annotation on the tests module tells Rust to compile and run this test code only when you run cargo test
, and not when you run cargo build
. This saves compile time when you only want to build the library and saves space in the resulting compiled artifact because the tests are not included.
The mod tests { }
block defines a new module named tests
. This is a conventional way of organizing test functions in Rust. Tests can be run with cargo test
.
If you run cargo test
, the terminal will return 3 passing tests!
Since Rust's testing framework, Cargo, runs tests in parallel by default, this can lead to nondeterministic behavior if the code being tested is not designed to handle concurrent execution safely (for example, keymap
with iterator
and AppendStore
). The immediate fix for this issue is to enforce the tests to run serially, thus avoiding the problems caused by concurrent access and modification. This can be achieved by using the following command:
This command tells Cargo to run the tests with only one thread, effectively serializing test execution and preventing concurrent access to shared resources.
Mock dependenices
The let mut deps = mock_dependencies();
line creates a new set of mock dependencies, which simulate the necessary dependencies of a smart contract in a testing environment. These dependencies include storage, a message handler, and an API client.
The let info = mock_info("creator", &[Coin { denom: "earth".to_string(), amount: Uint128::new(1000), }], );
line creates a new mock transaction context, which simulates the context in which a transaction would be executed on the blockchain. This context includes information about the sender of the transaction (in this case, the creator), as well as any tokens (in this case, a single "earth" token with a balance of 1000).
Taken together, these lines set up a mock environment in which the counter contract can be tested. The proper_initialization()
function can now perform its tests using these mock dependencies and transaction context.
Init_msg
The let init_msg = InstantiateMsg { count: 17 };
line creates a new InstantiateMsg
struct with an initial count value of 17. This message is used to initialize the counter contract's state.
The let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap();
line instantiates the smart contract with the given dependencies that we defined above, as well as the transaction context, and instantiation message. This initializes the smart contract's state and returns a Response
struct that contains any messages that the contract emits during initialization.
The assert_eq!(0, res.messages.len());
line checks that no messages were emitted during smart contract instantiation. If any messages were emitted, this assertion would fail and the test would panic.
The let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
line queries the smart contract's state using the QueryMsg::GetCount
message. This retrieves the current count value stored in the smart contract's state.
The let value: CountResponse = from_binary(&res).unwrap();
line deserializes the query response data (which is in binary format) into a CountResponse
struct. This struct represents the result of the query and contains the current count value.
Finally, the assert_eq!(17, value.count);
line checks that the current count value retrieved from the smart contract's state is equal to the expected value of 17. If the current count value is not equal to 17, this assertion would fail and the test would panic.
An example of this technique being used in production is .
, which contains the proper_initialization()
test and tests whether or not the counter contract is properly instantiated. Let's break this function down line-by-line so we have a thorough understanding of each piece of the code.
The use super::*;
line imports all the modules from the parent module ( of the contract.rs
file). This allows the test module to access contract.rs
's imports and test its functionality.
Remember, , which is why this struct is required to instantiate the contract. If there was no instantiation struct with a starting count, the test would fail.
With this information, examine the other testing functions in the counter contract. You should have all of the resources you need to start writing your very own unit tests! Now let's learn about Multitests!
Uint128
is a data structure designed to work with usigned 128-bit integers.
If you are familiar with Rust, you might know that it has it's own native primitive - u128
. Uint128
differs from u128
in that it is a string encoded integer, rather than the traditional little/big-endian.
Simliarly, cosmwasm-std
also has Uint64
and Uint256
and all of the following applies there as well.
Uint128
is a thin wrapper around u128
that uses strings for JSON encoding/decoding, such that the full u128
range can be used for clients that convert JSON numbers to floats, like JavaScript and jq (source).
If you are familiar with Messages, you already know that most of the time we will use serde to deserialize them from JSON (if not, you should read on contract entrypoints and the concept of Messages). Output will often be serialized in the same way.
In general, JSON implementations usually accept [-(2^53)+1,(2^53)-1]
as an acceptable range for numbers. So if we need more than that (for example for 64(unsigned), 128 and 256 numbers) we'll want to use a string-encoded numbers. That's why we'll prefer to use Uint128
in entrypoint messages, for example:
Depends on the needs of your contract, you can choose to use either Uint128
or u128
.
As a rule of thumb, most of the time you will want to store numbers as u128
rather than Uint128
.
More specifically, since Uint128
is a string encoded number the storage space it'll consume will depend on the number of digits of the number you are storing. u128
on the other hand will always take a constant amount of storage space. That's why Uint128
will be more efficient for very small numbers (and then, why use 128-bit integer to begin with?), while u128
will be more efficient for most use cases.
Example:
Floating points are a big no-no in blockchain. The reason being, and without diving into too much detail, that floating point operations might be non-deterministic, so different nodes in the blockchain might get different results and not reach consensus.
That being said, there are different ways to overcome this.
Sometimes you can absorb some lack of precision, and you can use integer division. For example, if you want to divide 1 million tokens between three addresses:
Note - integer division in Rust will always round down towards zero (source).
You can often increase integer division's precision by enlarging your inputs by some factor. When you are done with the calculations, you can shrink the number to the original scale.
Let's look at an example calculation with the following inputs:
Not scaling up before the calculation causes loss of precision:
Instead, we can first scale up the inputs by a constant SCALE_FACTOR
, and shrink them back down at the end:
Scale factors can be as big as possible, provided they don't cause overflows.
If you still need decimals in your code, a fixed-point decimals library can assist you.
There are several Rust libraries that implement fixed-point decimals, but you'd probably be best to use Cosmwasm's own Decimal
library.
Keep in mind that using fixed-point decimals comes with an overhead (both efficiency and ease of use), so you would prefer to avoid it if possible.
Sometimes even when you don't use floats directly, one of your contract's dependencies do. In that case you'd want to turn off the feature that using the floats or just replace the library altogether.
But the hard part is to identify what causes the problem to begin with. It might get pretty complicated, and probably a bit too involved for this doc, but there's this greate article that was published in the Cosmwasm blog that is very helpful for this.
A new feature in Secret Network v1.9 is Execution Finalization.
Execution Finalization is a concept where the contract notifies the chain that the current execution must be the last massage in a single transaction. Or, in other words, the last part of an execution unit. This will cause the chain to revert the transaction if any action is taken after this flag is set.
Secret Network (and in fact all Cosmos chains) run discrete blocks of execution instructions called transactions. A single transaction can contain multiple messages. Each message contains a specific instruction. Any failure of any message in the transaction will cause the entire transaction to be reverted. This behavior can be exploited by attackers (or even non-maliciously) to select only favorable scenarios to be executed, while the unfavorable ones to be reverted.
For example, consider a dice game. The user sends some bet. If he makes the correct bet, he will receive his original bet plus his winnings. Otherwise, his bet is lost. An attacker can abuse such logic by sending a transaction with two messages. The first is the bet message, while the second is message that will conditionally fail if the bet is lost. This way, he is guaranteed to make a profit.
This same behavior can also be used for flash-loans, which are not inherently malicious but can be used to leverage capital for exploitation without risk of significant loss.
The new FinalizeTx message is now available for developers to toggle in case a specific execution path should be the last in a transaction. That way, it is guaranteed to be executed as the contract expects and cannot be reverted by the user.
To use FinalizeTx, simple append the FinalizeTx message when returning a result from your contract:
Messages are executed FIFO (first in, first out). If you want to send other messages before finalizing the execution, simply append them to the response before the FinalizeTx message.
We suggest developers consider whether or not a user conditionally reverting transactions is behavior that may affect the security of their application, as it may allow for scenarios that reveal confidential information or result in loss of funds. That being said, the trade-off is that such executions cannot be chained with other contract interactions, so usage of this feature should not be used without careful consideration.
We recommend when writing Secret Contracts to leverage a CI platform to create a continuous integration flow for your contract.
The two types of tests to focus on:
Unit Tests - tests that are executed only in the context of the contract itself
Integration Tests - tests that include executing contract flows on a running chain (LocalSecret or Secret testnet)
Our platform of choice is GitHub Actions. GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository, or deploy merged pull requests to production.
Here are examples of how to implement these flows in your own contracts:
Integration Tests - https://github.com/scrtlabs/SecretJack/blob/master/.github/workflows/Integration.yml\
Note that in this example the unit test flow executes the main command make unit-test
, while the integration tests initialize a LocalSecret instance, and execute a Typescript file that handles deployment, initialization and execution of the test logic. You can find that file here.
Optionally perform state migrations of Secret Network smart contracts
Contracts on Secret Network can be initialized as migratable, which allows the contract administrator to upload a new version of the contract and then send a migrate
message to move to the new code, which "migrates" storage from a previous contract to the new contract.
There are two key components to a migratable contract:
A designated admin
whose address will be allowed to perform migrations.
The availability of a MigrateMsg
transaction.
On instantiation
, the contract creator can specify an admin
address, which can be the contract creator's wallet address, an external account, or a governance contract, etc. Whatever the address is, this admin
gains the ability to migrate the contract to a new codeId
and codeHash
, and can also update or clear the admin
address. When the admin
invokes the MigrateMsg
message, the migrate()
function is called on the new contract, where the new contract can optionally perform state migrations from an old contract.
Once the migrate()
function is invoked, the contract address now points to the new code, and anybody contacting that address will reach the new code. The old code becomes unreachable.
Performing a contract migration is a three step process:
Write a newer version of the contract you wish to update
Upload the new smart contract, but don’t instantiate it
Use a dedicated MigrateMsg transaction to point the new contract to the code you wish to migrate
When migrating, the new contract must have a migrate
function as an entry_point.
The migrate
function provides the ability to make any desired changes to the contract's state, similar to a database migration.
If the migrate
function returns an error, the transaction will abort and no state changes will occur.
Secret contracts instantiated prior to the v1.11 network upgrade can be migrated via a SCRT governance signaling proposal. If approved for migration, the contracts' code hashes will be matched to a chosen admin key and added to a list which is hardcoded inside the enclave for reference.
In order for a Secret contract instantiated after the v1.11 network upgrade to be migrated, the contract must be instantiated with an admin. To query whether or not a Secret contract was instantiated with an admin
, use the contractInfo
method:
The query will return the admin address, if there is one:
To query if a Secret contract was migrated successfully, use the contractHistory
method:
The method is designed to retrieve the history of a contract, specifically its code changes over time. The method returns an object containing an array of ContractCodeHistoryEntry
items.
Here all interaction SDKs are listed that are maintained for the Secret Network
This page is dedicated to community tutorials and code repositories that provide helpful references for writing Secret Network contracts as well as building full-stack Secret Networks dApps.
To master the SNIP-20 contract specifications, you can refer to these two key parts:
To master the SNIP-721 contract specifications for Non-Fungible Tokens (NFT), you can refer to these two key parts:
Here are some examples of additional Secret Contracts for reference:
- Winning projects from past Secret hackathons
- This is part of the . It is meant as an easy to understand first contract example that solves the Millionaire's Problem.
- Quick-start template to start developing Secret Contracts.
- Includes messages for frequently used permissioned functionality.
- Reference implementation of Secret Tokens.
- Reference Implementation of Private Multitokens.
- Reference implementation of Secret NFTs.
- Demonstrates how to use a parent contract to create offspring contracts.
- Example of a contract that can be uploaded to both secret and non-secret cosmwasm chains.
The Secret Software Development Kit (SDK) in Python is a simple library toolkit for building software that can interact with the Secret blockchain and provides simple abstractions over core data structures, serialization, key management, and API request generation.
Written in Python offering extensive support libraries
Versatile support for key management solutions
Exposes the Secret Rest API through LCD/REST client
See the full documentation, code and usage examples here:
A toolkit very similar to CosmJs that allows people to create and sign contract executions, integrate wallets and more.
Documentation for Secret.Js is separately hosted under:
- Simple betting game on the outcome of a dice roll, with a video tutorial.
- Example of a voting contract.
- Template for launching NFT projects that need randomized minting.
- Verifies that an on-chain contract is indeed running a specific code. .
- A smart contract that receives tokens and distributes them to multiple addresses. .
- Get authorized to web3 places by proving you are the owner of an NFT. .
- A wallet that is collectively controlled by multiple accounts.
- Example how real-world data could be inserted on chain by validators.
.
- Demonstrates Vesting implementation
- These are the standard specifications and reference contract that implements the base standard required for fractionalized NFTs on Secret Network.
As explained in the Gas and fee section Secret computational load is denominated in gas
. The amount of gas that can be spend at every block is capped via the max_gas
block parameter. This cap is currently set at 6,000,000 (6 million) gas
meaning a block will never contain more computational load than what is represented by 6 million gas. The computational load per gas unit is calculated and then denominated in the chain binary.
You can find the Gas settings for Secret Network in the Network code here - [SDK/Go message types] , [Wasm messages]
The max_gas
parameter is chosen in such a way that validators can ensure they process the full computational load within the target block execution time (~6s). If this computation and expected blocktime don't align the blocks will become longer and longer as they await 66% of voting power to sign the block. When block time increases the TPS of the blockchain drastically decreases only complicating the situation further.
You can read more about this interplay between blocktime, TPS and scalability in this post mortem of the Shade airdrop.
One can expect the overhead of all enclave and encryption transactions to be roughly 30% over a vanilla wasm engine. This overhead comes from the decryption and re-encryption done in the enclave and the varying verification's that are required of the message input data. This overhead does not exist for standard SDK messages like "stake", "transfer", "vote" etc.
Additionally Secret leverages an older engine for the wasm VM dubbed "Wasm3". The vanilla engine "Wasmer" is roughly10-15x more performant. This engine is not yet supported as there is no Enclave compatible version, development effort to support it though is underway as listed in the core roadmap.
Because of above factors the TPS of Secret is limited in comparison to vanilla Tendermint and Cosmos SDK benchmarks. Based on an average of 100.000 gas for a contract interaction (something like an NFT mint or non-routed DEX swap) Secret can process 60 TXs per block which at ~6s per block 10 WASM transactions per second. SDK interactions take a lot less gas meaning the overall TPS of Secret in practice is ~25.
As explained in the above section this number can still be significantly increased by upgrading the WASM engine but has proven sufficient so far in periods of high demand
Important to note is that Queries related to contract state require the API node to access the enclave, these are therefore more resource expensive than SDK queries. Secret API infrastructure needs to be scaled accordingly to fit heavy load user interfaces. One can expect to require more Secret API nodes than standard Cosmos API nodes for a similar application. As transaction complexity overhead decreases with better engines so does increase the ability for a node to process queries. Verified queries on Secret have already become a hundred times more performant with 1.4-1.6 upgrades, and with the current community API they are in a very stable spot.
Short term scalability improvements can come from a WASM engine replacement and increase in Light client verification methods so to lower the enclave load. Also storage access can become more performant with an SGX backend change which is on the short term roadmap. Long term Secret does not have to worry about blockspace with the plenty opportunities of replicated security and the cosmos sdk. For more on the core development roadmap check out the documentation here.
Full example guide on mitigating privacy risks.
Here we use a sample contract for selling a secret message to illustrate padding, gas evaporation, and side-chain attack prevention using a trusted actor to confirm transactions. In the contract we define three methods: posting a secret for sale, buying a secret, and confirming purchases. All three message types have two optional parameters: gas_target
and padding
. These will be used to help mask which message is being called.
execute
Padding and evaporation are parameters that are set for all message types.
The padding
parameter is used by the client to pad the size of message being sent to the contract. The padding
parameter is not used within the contract itself and is only necessary when using Keplr's amino format. In other cases the dapp or wallet can simply add extra space characters to the end of the json message.
We also want to pad the result sent back to the client. We can use the pad_handle_result
function from secret toolkit to format the response to a fixed block size. If you have responses that are large, you will need a sufficiently large block size to successfully mask the method being run.
After padding the result at the end of execute
entry point function, we use gas_target
along with the evaporation api functions to evaporate any remaining gas to hit the gas target. Note, we simply ignore both the padding
and the gas_target
message parameters using ..
in the match statement.
Just like we want to use padding to hide the message type when sending between the client and the contract, the number of bytes written to the chain can leak information about what is written. For example, a coin amount stored as a Uint128
will write a different number of bytes depending on the size of the number, and also in our example the secret message could leak information based on its length. For data structs we want to store on chain we usually create a stored version and create conversion methods between the unstored and stored versions. Note in the example, we use the secret toolkit space_pad
function to make all secrets stored as 256 bytes long masking the content.
Padding and evaporation help to protect privacy about what types of functions are being called in a contract and about what data is being read and written from storage. However, our contract is still vulnerable to a side chain attack if a buyer can simply purchase a secret without a trusted actor confirming the transaction. This is because a malicious actor could fork the chain and buy the secret on the forked chain without ever paying any coins on the mainnet.
In our case a trusted actor who can confirm the purchase is the seller of the secret. If all purchases must have the ConfirmPurchase
transaction performed after buying, then it is impossible for the side-chain attack to occur. Other contracts that need to implement this functionality might require a trusted third-party actor instead, for example a game contract where turns are executed on-chain.
You can use the Feegrant module or Smart contract based fee abstraction to simplify the usage of your dApp as explained here!
Secret also allows for Gas abstraction for users by leveraging the CosmosSDK FeeGrant module. This module allows one to submit transactions where a different wallet is paying the gas fees as long as they granted you a budget to do that.
Documentation to create Feegrant functionality in your UI are here. This tool is widely used in different Secret UIs (for ex: Secret dashboard) and there is a community run FeeGrant faucet available for dApps to use. - Faucet - Code
You can use Smart-contracts with Fees deposited in them to abstract fee usage and automate tasks for users - An example of this is the Sienna rewards contract which deposits users rewards into their wallet daily. There is also the feature of "opportunistic execution" which allows additional privacy to perform automated actions by using leftover gas from the Gas evaporation function in the Secret compute module. - Here is a great guide on opportunistic execution!
Secret has integrations with the following wallets:
Keplr
Leap
Fina.Cash (mobile friendly)
Starshell (added privacy)
Metamask (only signing, not SCRT storage)
Cosmostation
Citadel.One
You can check out app.shadeprotocol.io or dash.scrt.network to try out these various wallets. Documentation for wallet integration is available here:
Secret Network is the only non-EVM cosmos chain that supports native Metamask signing! It is an awesome onboarding tool for new users or can be the missing tool to have people use Secret in your Privacy as a Service flow without ever needing a 2nd wallet or other gas.
Helpful references for writing Secret Network contracts as well as building full-stack Secret Networks dApps.
Shade Protocol - Integrated Defi dApp with Stablecoin, lending, AMM and more.
Secret Blackjack - Secret multiplayer gaming example - be the first player among multiple opponents to empty your hand!
Secret Admirers - Video streaming dApp with privacy preserving features.
Private Voting - Private EVM cross-chain voting dApp deployed on Polygon testnet (source code).
Secret Lottery - Lottery dApp that returns a number between 1 and 1,000,000 using SecretVRF (source code).
Secret Business Cards - Business card dApp that uses Secret toolkit viewing keys to create permissioned business cards (source code).
Millionaire's Problem - Privately reveal whose networth is greater with Secret contracts (source code).
Mystic Skulls - Upgradable NFT collection with puzzles. Frontend.
Dreamscape - Multiplayer card game. Frontend.
SecretJack - BlackJack game.
SecretSwap - Decentralized Exchange of tokens and SCRT. Frontend.
Secret Vault - Simple Password Manager.
Decure - Customer review platform.
SecretHoldEm - Texas hold'em poker game.
This section provides a detailed breakdown of a Secret smart contract known as the Millionaire's problem. Going through this section won't require you to know Rust (though it would help), but instead we'll focus mostly on the logic, and how different components interact with each other.
We'll start by going through the basic components of a contract.
Instantiate - the logic that is ran once on initialization of the contract. This will usually be the initial conditions and configuration of the contract.
Execute - this is where the transactional logic resides. These are functions that modify contract data
Query - these are functions that are ran as read-only and cannot modify contract data
State - the long-term storage of the contract. By default, data used by contracts will be lost between transactions.
Each contract will contain these basic building blocks, with their logic being built around how they work.
Did you know? Executes require a transaction, which costs gas. Queries are free! This is why it is preferable to use queries when an action does not require modifying data.
Now we can move on to examining our contract, which solves the Millionaire's problem. We'll start by looking at the directory structure:
For the most part, we can expect the files to contain the following data:
contract.rs - will contain most of the business logic of the app
msg.rs - will contain the various interfaces and messages that are used to communicate with the contract
state.rs - will contain logic pertaining to data structures that are saved in the long-term storage (state)
cargo.toml - will contain metadata and detail the various libraries that the contract is using
Did you know? Contracts for Secret Network depend on forked versions of the standard CosmWasm dependencies
Before we jump into the code, we will go over the contract flow. We have to accept inputs from two different users, save their inputs, and expose a way to check which input is greater.
We end up with the following state machine:
This is a fairly naïve approach, but for the purposes of this example it is fairly straightforward. From looking at this state machine, we can expect that the implementation will contain (at minimum) the following methods:
Submit Input - allow the user to submit input. We'll also need the user to identify himself somehow so we can print out his identity if he is the winner.
Query Result - ask the contract which input is greater and return the id of the user whose input was greater
Reset - resets the state and start over
We'll note that Submit Input and Reset are both actions that require modifying contract data, so they will be executes. Similarly, Query Result is a read-only function, so it can be implemented (as the name suggests) as a query.
Now let's think about what data our contract will need to store. Each submit input method will require a separate transaction, so we need to save both the user data, and something to help keep track of which step we are on. This means that our state will look something like:
User data #1 - <User ID, Net Worth>
User data #2 - <User ID, Net Worth>
Step - <Initial/Got 1st/Done>
Further Reading: For simplicity we do not differentiate between a transaction and a message. In reality, the Cosmos-SDK defines each discrete interaction with the chain as a message. While a transaction is a higher-level object that can contain multiple messages
Alright, now we are armed with all the knowledge we need to dive into the code itself!
Entry points, or handlers are where messages or queries are handled by the contract - this is basically where all data coming into the contract ends up. We can see the different entry points based on the contract components we described.
Looking at our contract, we can see these entry points defined in contract.rs -
The instantiate step is where we'll want to create a default contract state with our initial starting conditions to prepare the contract for accepting data. We're not depending on any input from the user in this step, so we can ignore all the function inputs.
Further Reading: The instantiate step is usually where contract ownership is defined, if the contract makes use of admin-only features
Let's look at this in the code:
Again, we'll ignore all the scary stuff and look only at lines 9 and 10.
On line #9 we're taking this yet unknown State data type and calling its default()
function.
On line #10 we're saving this State data structure to the contract storage. This means it will be available for us to read the next time the contract is called (from the execute or query entry-points). We can also see deps.storage
is being used as a function parameter. This gives us a clue as to what the deps
variable contains - objects that allow the contract to interact with functionality outside the contract code itself. Some examples of this are using long-term storage, calling efficient crypto APIs, or querying the blockchain state.
Did you know? The instantiate function can only be called once per contract when it is initialized
Since we want to understand what this strange State structure is, we can sort of guess that there might be some useful information in the state.rs file. And indeed here we find our definitions:
As per usual, let's ignore all that#[derive]
stuff, and instead look at our data structures. We can see that our State
data structure contains the exact types that we expected when thinking about the design of the contract. We'll also note that we chose to contain the user data in the Millionaire
struct.
Do you even Rust? The #[derive] header tells the compiler to provide implementations for basic behaviour. For example, using _Default
_ will tell our compiler to generate the ::default()
function, which will allocate the structure and set all the values to their defaults (0 for a number, "" for a string, etc.)
Switching back to contract.rs we'll just dive right into our execute entry-point, since we already know what to expect
Without understanding anything else, we can immediately see our 2 actions that we talked about previously implemented here. We can also figure out that msg
is some data type that tells us what function we need to call.
Do you even Rust? The match syntax is logically similar to switch-case that you might be familiar with from other languages
Toggling over to msg.rs **** we can see what ExecuteMsg
is defined as:
Do you even Rust? In Rust Enums can contain complex data types, which makes them especially useful for interface definitions
Yep, as we expect, ExecuteMsg
is just an enumeration of the different message types (and their parameters) that a user can send to the contract. The way this works in practice is that the user sends his data as a JSON object. This object then gets parsed and matched according to the definition in ExecuteMsg
. If the object does not match one of the enum types? The transaction will fail and get rejected!
Do you even Rust? Can you intuitively guess what #[derive(Serialize, Deserialize, PartialEq)] are used for? How about #[serde(rename_all = "snake_case")]?
Okay, time to zoom back in to contract.rs and take a look at our functions. The first one we will be looking at is try_submit_net_worth:
This is the main function that handles the logic of our contract. Although you can probably figure this out easily, we'll describe the logic you're seeing:
Read the current state of the contract from long-term storage
If we don't have any data, save user data as player#1 and set the state as Got1
If we already got the data for the first user, save user data as player#2 and set the state as Done
Save the new state in storage.
Super simple. Let's head over to the try_reset function
At this point I don't even have to explain to you what's going on here. We'll just note that the Response
object in this case returns something called an attribute. An attribute is a key-value pair that gets returned after a successful message that can help summarizing what happened. Attributes can even be indexed, queried and used as event triggers via WebSocket.
Now we have all the pieces in place. All that remains to be done is to look at how the result is queried by the user.
Let's see what the last contract component we haven't looked at - query - has for us:
At a glance, we see the same basic structure that we've seen in the Execute entry point. We can also guess that msg
and the QueryMsg
data type are functionally similar to the ExecuteMsg
type we've seen earlier. By now you'll know where this data type is defined, so you can go take a look at the definition and verify this assumption.
Looking a bit deeper, the to_binary
method sticks out as odd. We haven't seen this in our executes or the instantiate function. The reason for this is that queries are returned as binary data. Executes and the instantiate function return complex objects that are saved to the blockchain state, whereas queries are simple and only need to return the specific data the user cares about.
The last piece of the puzzle is the query_who_is_richer
function
Logically, this is super simple. Read the state, check which player has the most money, and return the result.
Do you even Rust? The astute reader will remember that player in this context is actually a complex data structure. How is it possible to call max(player1, player2) or to check if player1 == player2? It turns out you can actually implement the logic for equality and ordering yourself for structs. Head over to state.rs to see an example of that in action
The only thing worth noting is that the response is of the type RicherResponse
(we ignore the StdResult
wrapper - it is used to handle and include errors in the possible return types). RicherResponse
is a custom type that we defined (in msg.rs).
Usually, you will want to define a custom return type for each separate query, which makes data easier to process on the user side - we'll remember that while we talk about user queries for simplicity, in reality, the user will most likely be accessing data through some web application which will be handling both querying the contract and processing the response.
That's it! An entire Secret Contract from start to end. Thanks for taking the time to go through all of this guide (or even a small portion of it)! You should now have a good understanding of the building blocks of a contract not only on Secret Network, but for all blockchains that support CosmWasm.
Intro to Secret Contracts - a more in-depth Secret Contract guide
CosmWasm Documentation - everything you want to know about CosmWasm
Secret.JS - Building a web UI for a Secret Contract