diff --git a/.github/workflows/ci-docs.yaml b/.github/workflows/ci-docs.yaml index b2703ccf..c868748a 100644 --- a/.github/workflows/ci-docs.yaml +++ b/.github/workflows/ci-docs.yaml @@ -81,13 +81,11 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Install forge doc deps + run: cargo install mdbook-pagetoc + - name: Build docs - run: | - forge doc --build - # Inject another /contracts/ for github.com URLs. - find sol/sapphire-contracts/book -name *.html | xargs sed -i -E "s+(blob/.*/contracts)+\1/contracts+" - # Remove /src/ from "Inherits" links. - find sol/sapphire-contracts/book -name *.html | xargs sed -i "s+/src/+/+" + run: pnpm doc - name: Deploy to api-reference branch uses: peaceiris/actions-gh-pages@v3 diff --git a/.github/workflows/contracts-test.yaml b/.github/workflows/contracts-test.yaml index 3ed3b0a9..913ad08b 100644 --- a/.github/workflows/contracts-test.yaml +++ b/.github/workflows/contracts-test.yaml @@ -56,6 +56,8 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Install forge doc deps + run: cargo install mdbook-pagetoc - name: Install dependencies run: pnpm install - name: Build JS client @@ -69,8 +71,7 @@ jobs: run: pnpm hardhat test --network sapphire-localnet-ci - name: Build docs working-directory: contracts - run: | - forge doc --build + run: pnpm doc - name: hardhat test examples/hardhat working-directory: examples/hardhat run: pnpm hardhat run --network sapphire-localnet scripts/run-vigil.ts diff --git a/contracts/.gitignore b/contracts/.gitignore index 6810a3fe..5db51688 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -5,3 +5,4 @@ coverage.json coverage/ gasReporterOutput.json typechain-types/ +sol/ diff --git a/contracts/README.md b/contracts/README.md index 58b71064..1a55d8c8 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -18,10 +18,18 @@ $ pnpm install @oasisprotocol/sapphire-contracts #### Usage -Once installed, you can import Sapphire contracts. +Once installed, you can import and use the Sapphire contracts as follows: ```solidity -import {Enclave} from "@oasisprotocol/sapphire-contracts/contracts/OPL.sol"; +pragma solidity ^0.8.13; + +import "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol"; + +contract RandomNumber { + function generateNumber() public view returns (uint) { + return uint(bytes32(Sapphire.randomBytes(32, ""))); + } +} ``` ## Documentation @@ -29,7 +37,19 @@ import {Enclave} from "@oasisprotocol/sapphire-contracts/contracts/OPL.sol"; See the user's guide for [Sapphire](https://docs.oasis.io/dapp/sapphire/) and [OPL](https://docs.oasis.io/dapp/opl/). -API reference is available at [api.docs.oasis.io](https://docs.oasis.io/sol/sapphire-contracts). +The generated API reference is hosted at +[api.docs.oasis.io](https://api.docs.oasis.io/sol/sapphire-contracts). + +Generating API docs locally requires Foundry and mdbook-pagetoc. To install +them and generate the docs execute: + +```shell +curl -L https://foundry.paradigm.xyz | bash +cargo install mdbook-pagetoc +pnpm doc +``` + +The API docs index will be located in `sol/sapphire-contracts/book/index.html`. ## Contribute diff --git a/contracts/book.toml b/contracts/book.toml new file mode 100644 index 00000000..5bd5fd28 --- /dev/null +++ b/contracts/book.toml @@ -0,0 +1,4 @@ +[preprocessor.pagetoc] +[output.html] +additional-css = ["theme/pagetoc.css"] +additional-js = ["theme/pagetoc.js"] diff --git a/contracts/contracts/ConsensusUtils.sol b/contracts/contracts/ConsensusUtils.sol index c84d7e46..272e1785 100644 --- a/contracts/contracts/ConsensusUtils.sol +++ b/contracts/contracts/ConsensusUtils.sol @@ -12,20 +12,23 @@ type StakingSecretKey is bytes32; /** * @title Consensus-level utilities - * @dev Generate Oasis wallets for use with staking at the consensus level + * @notice Generate Oasis wallets for use with staking at the consensus level. */ library ConsensusUtils { - /// The unique context for v0 staking account addresses. - /// https://github.com/oasisprotocol/oasis-core/blob/master/go/staking/api/address.go#L16 + /** + * @notice The unique context for v0 staking account addresses. + * @custom:see @oasisprotocol/oasis-core :: go/staking/api/address.go + */ string private constant ADDRESS_V0_CONTEXT_IDENTIFIER = "oasis-core/address: staking"; uint8 private constant ADDRESS_V0_CONTEXT_VERSION = 0; /** - * @dev Generate a random Ed25519 wallet for Oasis consensus-layer staking - * @param personalization Optional user-specified entropy - * @return publicAddress Public address of the keypair - * @return secretKey Secret key for the keypair + * @notice Generate a random Ed25519 wallet for Oasis consensus-layer + * staking. + * @param personalization Optional user-specified entropy. + * @return publicAddress Public address of the keypair. + * @return secretKey Secret key for the keypair. */ function generateStakingAddress(bytes memory personalization) internal @@ -47,8 +50,8 @@ library ConsensusUtils { } /** - * @dev Derive the staking address from the public key - * @param ed25519publicKey Ed25519 public key + * @notice Derive the staking address from the public key. + * @param ed25519publicKey Ed25519 public key. */ function _stakingAddressFromPublicKey(bytes32 ed25519publicKey) internal @@ -64,10 +67,10 @@ library ConsensusUtils { } /** - * @dev Derive an Oasis-style address - * @param contextIdentifier Domain separator - * @param contextVersion Domain version - * @param data Public point of the keypair + * @notice Derive an Oasis-style address. + * @param contextIdentifier Domain separator. + * @param contextVersion Domain version. + * @param data Public point of the keypair. */ function _addressFromData( string memory contextIdentifier, diff --git a/contracts/contracts/EIP155Signer.sol b/contracts/contracts/EIP155Signer.sol index c7be1f0c..33de1cb7 100644 --- a/contracts/contracts/EIP155Signer.sol +++ b/contracts/contracts/EIP155Signer.sol @@ -20,9 +20,9 @@ library EIP155Signer { } /** - * Encode a signed EIP-155 transaction - * @param rawTx Transaction which was signed - * @param rsv R, S & V parameters of signature + * @notice Encode a signed EIP-155 transaction. + * @param rawTx Transaction which was signed. + * @param rsv R, S & V parameters of signature. */ function encodeSignedTx(EthTx memory rawTx, SignatureRSV memory rsv) internal @@ -43,10 +43,11 @@ library EIP155Signer { } /** - * Sign a raw transaction, which will then need to be encoded to include the signature - * @param rawTx Transaction to sign - * @param pubkeyAddr Ethereum address of secret key - * @param secretKey Secret key used to sign + * @notice Sign a raw transaction, which will then need to be encoded to + * include the signature. + * @param rawTx Transaction to sign. + * @param pubkeyAddr Ethereum address of secret key. + * @param secretKey Secret key used to sign. */ function signRawTx( EthTx memory rawTx, @@ -64,10 +65,10 @@ library EIP155Signer { } /** - * Sign a transaction, returning it in EIP-155 encoded form - * @param publicAddress Ethereum address of secret key - * @param secretKey Secret key used to sign - * @param transaction Transaction to sign + * @notice Sign a transaction, returning it in EIP-155 encoded form. + * @param publicAddress Ethereum address of secret key. + * @param secretKey Secret key used to sign. + * @param transaction Transaction to sign. */ function sign( address publicAddress, diff --git a/contracts/contracts/EthereumUtils.sol b/contracts/contracts/EthereumUtils.sol index 62cdba1d..9c2987fd 100644 --- a/contracts/contracts/EthereumUtils.sol +++ b/contracts/contracts/EthereumUtils.sol @@ -46,9 +46,9 @@ library EthereumUtils { error k256DeriveY_Invalid_Prefix_Error(); /** - * Recover Y coordinate from X coordinate and sign bit - * @param prefix 0x02 or 0x03 indicates sign bit of compressed point - * @param x X coordinate + * @notice Recover Y coordinate from X coordinate and sign bit. + * @param prefix 0x02 or 0x03 indicates sign bit of compressed point. + * @param x X coordinate. */ function k256DeriveY(uint8 prefix, uint256 x) internal @@ -73,10 +73,10 @@ library EthereumUtils { error k256Decompress_Invalid_Length_Error(); /** - * Decompress SEC P256 k1 point - * @param pk 33 byte compressed public key - * @return x coordinate - * @return y coordinate + * @notice Decompress SEC P256 k1 point. + * @param pk 33 byte compressed public key. + * @return x X coordinate. + * @return y Y coordinate. */ function k256Decompress(bytes memory pk) internal @@ -101,10 +101,10 @@ library EthereumUtils { } /** - * Convert SEC P256 k1 curve point to Ethereum address - * @param x coordinate - * @param y coordinate - * @custom:see https://gavwood.com/paper.pdf (212) + * @notice Convert SEC P256 k1 curve point to Ethereum address. + * @param x X coordinate. + * @param y Y coordinate. + * @custom:see https://gavwood.com/paper.pdf (pp. 212) */ function toEthereumAddress(uint256 x, uint256 y) internal @@ -119,17 +119,22 @@ library EthereumUtils { error DER_Split_Error(); /** - * Extracts the `r` and `s` parameters from a DER encoded ECDSA signature. + * @notice Extracts the `r` and `s` parameters from a DER encoded ECDSA + * signature. * - * The signature is an ASN1 encoded SEQUENCE of the variable length `r` and `s` INTEGERs. + * The signature is an ASN1 encoded SEQUENCE of the variable length `r` and + * `s` INTEGERs. * + * ``` * | 0x30 | len(z) | 0x02 | len(r) | r | 0x02 | len(s) | s | = hex value * | 1 | 1 | 1 | 1 | 1-33 | 1 | 1 | 1-33 | = byte length + * ``` * - * If the highest bit of either `r` or `s` is set, it will be prefix padded with a zero byte - * There is exponentially decreasing probability that either `r` or `s` will be below 32 bytes. - * There is a very high probability that either `r` or `s` will be 33 bytes. - * This function only works if either `r` or `s` are 256bits or lower. + * If the highest bit of either `r` or `s` is set, it will be prefix padded + * with a zero byte. There is exponentially decreasing probability that + * either `r` or `s` will be below 32 bytes. There is a very high + * probability that either `r` or `s` will be 33 bytes. This function only + * works if either `r` or `s` are 256bits or lower. * * @param der DER encoded ECDSA signature * @return rsv ECDSA R point X coordinate, and S scalar @@ -214,13 +219,15 @@ library EthereumUtils { } /** - * Convert a Secp256k1PrehashedKeccak256 signature to one accepted by ecrecover - * @param pubkey 33 byte compressed public key - * @param digest 32 byte pre-hashed message digest - * @param signature ASN.1 DER encoded signature, as returned from `Sapphire.sign` - * @return pubkeyAddr 20 byte Ethereum address - * @return rsv Ethereum EcDSA RSV signature values - * @custom:see https://gavwood.com/paper.pdf (206) + * @notice Convert a Secp256k1PrehashedKeccak256 signature to one accepted + * by ecrecover. + * @param pubkey 33 byte compressed public key. + * @param digest 32 byte pre-hashed message digest. + * @param signature ASN.1 DER encoded signature, as returned from + * [`Sapphire.sign`](../Sapphire.sol/library.Sapphire.md#sign). + * @return pubkeyAddr 20 byte Ethereum address. + * @return rsv Ethereum EcDSA RSV signature values. + * @custom:see https://gavwood.com/paper.pdf (pp. 206) */ function toEthereumSignature( bytes memory pubkey, @@ -252,9 +259,10 @@ library EthereumUtils { } /** - * Generates an Ethereum compatible SEC P256 k1 keypair and corresponding public address - * @return pubkeyAddr Ethereum address - * @return secretKey Secret key used for signing + * @notice Generate an Ethereum compatible SEC P256 k1 keypair and + * corresponding public address. + * @return pubkeyAddr Ethereum address. + * @return secretKey Secret key used for signing. */ function generateKeypair() internal diff --git a/contracts/contracts/RLPWriter.sol b/contracts/contracts/RLPWriter.sol index 733b09bd..06b67cd7 100644 --- a/contracts/contracts/RLPWriter.sol +++ b/contracts/contracts/RLPWriter.sol @@ -2,19 +2,17 @@ pragma solidity ^0.8.0; /** - * @custom:attribution https://github.com/bakaoh/solidity-rlp-encode - * @custom:attribution https://raw.githubusercontent.com/Maia-DAO/solidity-rlp-encoder/main/src/rlp/RLPWriter.sol * @title RLPWriter * @author RLPWriter is a library for encoding Solidity types to RLP bytes. Adapted from Bakaoh's - * RLPEncode library (https://github.com/bakaoh/solidity-rlp-encode) with minor - * modifications to improve legibility. + * RLPEncode library (https://github.com/bakaoh/solidity-rlp-encode) with minor + * modifications to improve legibility. + * @custom:attribution https://github.com/bakaoh/solidity-rlp-encode + * @custom:attribution https://raw.githubusercontent.com/Maia-DAO/solidity-rlp-encoder/main/src/rlp/RLPWriter.sol */ library RLPWriter { /** * @notice RLP encodes a byte string. - * * @param _in The byte string to encode. - * * @return The RLP encoded string in bytes. */ function writeBytes(bytes memory _in) internal pure returns (bytes memory) { @@ -31,9 +29,7 @@ library RLPWriter { /** * @notice RLP encodes a list of RLP encoded byte byte strings. - * * @param _in The RLP encoded byte strings. - * * @return The RLP encoded list of items in bytes. */ function writeList(bytes memory _in) internal pure returns (bytes memory) { @@ -42,9 +38,7 @@ library RLPWriter { /** * @notice RLP encodes a list of RLP encoded byte byte strings. - * * @param _in The list of RLP encoded byte strings. - * * @return The RLP encoded list of items in bytes. */ function writeList(bytes[] memory _in) @@ -58,9 +52,7 @@ library RLPWriter { /** * @notice RLP encodes a string. - * * @param _in The string to encode. - * * @return The RLP encoded string in bytes. */ function writeString(string memory _in) @@ -73,9 +65,7 @@ library RLPWriter { /** * @notice RLP encodes an address. - * * @param _in The address to encode. - * * @return The RLP encoded address in bytes. */ function writeAddress(address _in) internal pure returns (bytes memory) { @@ -84,9 +74,7 @@ library RLPWriter { /** * @notice RLP encodes a uint. - * * @param _in The uint256 to encode. - * * @return The RLP encoded uint256 in bytes. */ function writeUint(uint256 _in) internal pure returns (bytes memory) { @@ -95,9 +83,7 @@ library RLPWriter { /** * @notice RLP encodes a bool. - * * @param _in The bool to encode. - * * @return The RLP encoded bool in bytes. */ function writeBool(bool _in) internal pure returns (bytes memory) { @@ -107,11 +93,10 @@ library RLPWriter { } /** - * @notice Encode the first byte and then the `len` in binary form if `length` is more than 55. - * + * @notice Encode the first byte and then the `len` in binary form if + * `length` is more than 55. * @param _len The length of the string or the payload. * @param _offset 128 if item is string, 192 if item is list. - * * @return RLP encoded bytes. */ function _writeLength(uint256 _len, uint256 _offset) @@ -144,9 +129,7 @@ library RLPWriter { /** * @notice Encode integer in big endian binary form with no leading zeroes. - * * @param _x The integer to encode. - * * @return RLP encoded bytes. */ function _toBinary(uint256 _x) private pure returns (bytes memory) { @@ -168,12 +151,11 @@ library RLPWriter { } /** - * @custom:attribution https://github.com/Arachnid/solidity-stringutils * @notice Copies a piece of memory to another location. - * * @param _dest Destination location. * @param _src Source location. * @param _len Length of memory to copy. + * @custom:attribution https://github.com/Arachnid/solidity-stringutils */ function _memcpy( uint256 _dest, @@ -204,12 +186,10 @@ library RLPWriter { } /** - * @custom:attribution https://github.com/sammayo/solidity-rlp-encoder * @notice Flattens a list of byte strings into one byte string. - * * @param _list List of byte strings to flatten. - * * @return The flattened byte string. + * @custom:attribution https://github.com/sammayo/solidity-rlp-encoder */ function _flatten(bytes[] memory _list) private diff --git a/contracts/contracts/Sapphire.sol b/contracts/contracts/Sapphire.sol index 785c501a..7cd740f7 100644 --- a/contracts/contracts/Sapphire.sol +++ b/contracts/contracts/Sapphire.sol @@ -3,7 +3,26 @@ pragma solidity ^0.8.0; /** * @title Sapphire - * @dev Convenient wrapper methods for Sapphire's cryptographic primitives. + * @notice This library provides a number of convenient wrappers for + * cryptographic operations such as the x25519 key derivation, Deoxys-II-based + * encryption and decryption, signing key generation, message digest signing and + * verification, gas padding and hashing. + * + * Most of the mentioned functions are implemented as Sapphire's precompiles and + * are cheap to call. + * + * #### Calling Precompiles Manually + * + * You can override the wrappers and call Sapphire precompiles by dispatching + * calls to specific well-known contract addresses, as described below. The + * __Precompile address__ section of each function will show you the address + * of the corresponding precompile. + * + * Input parameters should be packed into a contiguous memory region with each + * chunk of data padded to 32 bytes as usual. The recommended way to construct + * parameter byte sequences in Solidity is with `abi.encode` and `abi.decode`, + * which will transparently handle things like putting `bytes` lengths in the + * correct position. */ library Sapphire { // Oasis-specific, confidential precompiles @@ -40,34 +59,79 @@ library Sapphire { type Curve25519SecretKey is bytes32; enum SigningAlg { - // Ed25519 signature over the provided message using SHA-512/265 with a domain separator. - // Can be used to sign transactions for the Oasis consensus layer and SDK paratimes. + /// Ed25519 signature over the provided message using SHA-512/265 with a domain separator. + /// Can be used to sign transactions for the Oasis consensus layer and SDK paratimes. Ed25519Oasis, - // Ed25519 signature over the provided message. + /// Ed25519 signature over the provided message. Ed25519Pure, - // Ed25519 signature over the provided prehashed SHA-512 digest. + /// Ed25519 signature over the provided prehashed SHA-512 digest. Ed25519PrehashedSha512, - // Secp256k1 signature over the provided message using SHA-512/256 with a domain separator. - // Can be used to sign transactions for the Oasis consensus layer and SDK paratimes. + /// Secp256k1 signature over the provided message using SHA-512/256 with a domain separator. + /// Can be used to sign transactions for the Oasis consensus layer and SDK paratimes. Secp256k1Oasis, - // Secp256k1 over the provided Keccak256 digest. - // Can be used to sign transactions for Ethereum-compatible networks. + /// Secp256k1 over the provided Keccak256 digest. + /// Can be used to sign transactions for Ethereum-compatible networks. Secp256k1PrehashedKeccak256, - // Secp256k1 signature over the provided SHA-256 digest. + /// Secp256k1 signature over the provided SHA-256 digest. Secp256k1PrehashedSha256, - // Sr25519 signature over the provided message. + /// Sr25519 signature over the provided message. Sr25519, - // Secp256r1 signature over the provided SHA-256 digest. + /// Secp256r1 signature over the provided SHA-256 digest. Secp256r1PrehashedSha256, - // Secp384r1 signature over the provided SHA-384 digest. + /// Secp384r1 signature over the provided SHA-384 digest. Secp384r1PrehashedSha384 } /** - * @dev Returns cryptographically secure random bytes. + * @notice Generate `num_bytes` pseudo-random bytes, with an optional + * personalization string (`pers`) added into the hashing algorithm to + * increase domain separation when needed. + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000001` + * + * #### Gas cost + * + * 10,000 minimum plus 240 per output word plus 60 per word of the + * personalization string. + * + * #### Implementation details + * + * The mode (e.g. simulation or "view call" vs transaction execution) is fed + * to TupleHash (among other block-dependent components) to derive the "key + * id", which is then used to derive a per-block VRF key from + * epoch-ephemeral entropy (using KMAC256 and cSHAKE) so a different key + * id will result in a unique per-block VRF key. This per-block VRF key is + * then used to create the per-block root RNG which is then used to derive + * domain-separated (using Merlin transcripts) per-transaction random RNGs + * which are then exposed via this precompile. The KMAC, cSHAKE and + * TupleHash algorithms are SHA-3 derived functions defined in [NIST + * Special Publication 800-185](https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf). + * + * #### DANGER: Prior to Sapphire ParaTime 0.6.0 + * + * All view queries and simulated transactions (via `eth_call`) would + * receive the same entropy in-between blocks if they use the same + * `num_bytes` and `pers` parameters. If your contract requires + * confidentiality you should generate a secret in the constructor to be + * used with view calls: + * + * ```solidity + * Sapphire.randomBytes(64, abi.encodePacked(msg.sender, this.perContactSecret)); + * ``` + * + * #### Example + * + * ```solidity + * bytes memory randomPad = Sapphire.randomBytes(64, ""); + * ``` + * * @param numBytes The number of bytes to return. - * @param pers An optional personalization string to increase domain separation. - * @return The random bytes. If the number of bytes requested is too large (over 1024), a smaller amount (1024) will be returned. + * @param pers An optional personalization string to increase domain + * separation. + * @return The random bytes. If the number of bytes requested is too large + * (over 1024), a smaller amount (1024) will be returned. */ function randomBytes(uint256 numBytes, bytes memory pers) internal @@ -82,10 +146,12 @@ library Sapphire { } /** - * @dev Generates a Curve25519 keypair. - * @param pers An optional personalization string used to add domain separation. + * @notice Generates a Curve25519 keypair. + * @param pers An optional personalization string used to add domain + * separation. * @return pk The Curve25519 public key. Useful for key exchange. - * @return sk The Curve25519 secret key. Pairs well with {`deriveSymmetricKey`}. + * @return sk The Curve25519 secret key. Pairs well with + * [deriveSymmetricKey](#derivesymmetrickey). */ function generateCurve25519KeyPair(bytes memory pers) internal @@ -108,7 +174,24 @@ library Sapphire { } /** - * @dev Derive a symmetric key from a pair of keys using x25519. + * @notice Derive a symmetric key from a pair of keys using x25519. + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000002` + * + * #### Gas cost + * + * 100,000 + * + * #### Example + * + * ```solidity + * bytes32 publicKey = ... ; + * bytes32 privateKey = ... ; + * bytes32 symmetric = Sapphire.deriveSymmetricKey(publicKey, privateKey); + * ``` + * * @param peerPublicKey The peer's public key. * @param secretKey Your secret key. * @return A derived symmetric key. @@ -125,9 +208,31 @@ library Sapphire { } /** - * @dev Encrypt and authenticate the plaintext and additional data using DeoxysII. + * @notice Encrypt and authenticate the plaintext and additional data using + * DeoxysII. + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000003` + * + * #### Gas cost + * + * 50,000 minimum plus 100 per word of input + * + * #### Example + * + * ```solidity + * bytes32 key = ... ; + * bytes32 nonce = ... ; + * bytes memory text = "plain text"; + * bytes memory ad = "additional data"; + * bytes memory encrypted = Sapphire.encrypt(key, nonce, text, ad); + * bytes memory decrypted = Sapphire.decrypt(key, nonce, encrypted, ad); + * ``` + * * @param key The key to use for encryption. - * @param nonce The nonce. Note that only the first 15 bytes of this parameter are used. + * @param nonce The nonce. Note that only the first 15 bytes of this + * parameter are used. * @param plaintext The plaintext to encrypt and authenticate. * @param additionalData The additional data to authenticate. * @return The ciphertext with appended auth tag. @@ -146,11 +251,34 @@ library Sapphire { } /** - * @dev Decrypt and authenticate the ciphertext and additional data using DeoxysII. Reverts if the auth tag is incorrect. + * @notice Decrypt and authenticate the ciphertext and additional data using + * DeoxysII. Reverts if the auth tag is incorrect. + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000004` + * + * #### Gas cost + * + * 50,000 minimum plus 100 per word of input + * + * #### Example + * + * ```solidity + * bytes32 key = ... ; + * bytes32 nonce = ... ; + * bytes memory text = "plain text"; + * bytes memory ad = "additional data"; + * bytes memory encrypted = Sapphire.encrypt(key, nonce, text, ad); + * bytes memory decrypted = Sapphire.decrypt(key, nonce, encrypted, ad); + * ``` + * * @param key The key to use for decryption. - * @param nonce The nonce. Note that only the first 15 bytes of this parameter are used. + * @param nonce The nonce. Note that only the first 15 bytes of this + * parameter are used. * @param ciphertext The ciphertext with tag to decrypt and authenticate. - * @param additionalData The additional data to authenticate against the ciphertext. + * @param additionalData The additional data to authenticate against the + * ciphertext. * @return The original plaintext. */ function decrypt( @@ -167,9 +295,55 @@ library Sapphire { } /** - * @dev Generate a public/private key pair using the specified method and seed. + * @notice Generate a public/private key pair using the specified method and + * seed. The available methods are items in the + * [`Sapphire.SigningAlg`](#signingalg) enum. Note, however, that the + * generation method ignores subvariants, so all three Ed25519-based are + * equivalent, and all Secp256k1 & Secp256r1 based methods are equivalent. + * Sr25519 is not available and will return an error. + * + * #### Precompile address + * `0x0100000000000000000000000000000000000005` + * + * #### Gas Cost + * + * ##### Ed25519: 1,000 gas + * + * - `0` (`Ed25519Oasis`) + * - `1` (`Ed25519Pure`) + * - `2` (`Ed25519PrehashedSha512`) + * + * ##### Secp256k1: 1,500 gas. + * - `3` (`Secp256k1Oasis`) + * - `4` (`Secp256k1PrehashedKeccak256`) + * - `5` (`Secp256k1PrehashedSha256`) + * + * ##### Secp256r1: 4,000 gas + * - `7` (`Secp256r1PrehashedSha256`) + * + * #### Public Key Format + * + * ##### Ed25519 + * + * 32 bytes + * + * ##### Secp256k1 & Secp256r1 + * + * 33 bytes, compressed format (`0x02` or `0x03` prefix, then 32 byte X + * coordinate). + * + * #### Example + * + * ```solidity + * bytes memory seed = hex"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + * bytes memory publicKey; + * bytes memory privateKey; + * (publicKey, privateKey) = Sapphire.generateSigningKeyPair(Sapphire.SigningAlg.Ed25519Pure, seed); + * ``` + * * @param alg The signing alg for which to generate a keypair. - * @param seed The seed to use for generating the key pair. You can use the `randomBytes` method if you don't already have a seed. + * @param seed The seed to use for generating the key pair. You can use the + * `randomBytes` method if you don't already have a seed. * @return publicKey The public half of the keypair. * @return secretKey The secret half of the keypair. */ @@ -185,11 +359,56 @@ library Sapphire { } /** - * @dev Sign a message within the provided context using the specified algorithm, and return the signature. + * @notice Sign a message within the provided context using the specified + * algorithm, and return the signature. The `context_or_digest` and + * `messages` parameters change in meaning slightly depending on the method + * requested. For methods that take a context in addition to the message you + * must pass the context in the `context_or_digest` parameter and use + * `message` as expected. For methods that take a pre-existing hash of the + * message, pass that in `context_or_digest` and leave `message` empty. + * Specifically the `Ed25519Oasis` and `Secp256k1Oasis` variants take both a + * context and a message (each are variable length `bytes`), the context + * serves as a domain separator. + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000006` + * + * #### Gas cost + * + * See below for the method-dependent base cost, plus 8 gas per 32 bytes of + * context and message except digest. + * + * #### Signing algorithms + * + * - `0` (`Ed25519Oasis`): 1,500 gas, variable length context and message. + * - `1` (`Ed25519Pure`): 1,500 gas, empty context, variable length message. + * - `2` (`Ed25519PrehashedSha512`): 1,500 gas, pre-existing SHA-512 hash + * (64 bytes) as context, empty message. + * - `3` (`Secp256k1Oasis`): 3,000 gas, variable length context and message + * - `4` (`Secp256k1PrehashedKeccak256`): 3,000 gas, pre-existing hash + * (32 bytes) as context, empty message. + * - `5` (`Secp256k1PrehashedSha256`): 3,000 gas, pre-existing hash (32 + * bytes) as context, empty message. + * - `7` (`Secp256r1PrehashedSha256`): 9,000 gas, pre-existing hash (32 + * bytes) as context, empty message. + * + * #### Example + * + * ```solidity + * Sapphire.SigningAlg alg = Sapphire.SigningAlg.Ed25519Pure; + * bytes memory pk; + * bytes memory sk; + * (pk, sk) = Sapphire.generateSigningKeyPair(alg, Sapphire.randomBytes(32, "")); + * bytes memory signature = Sapphire.sign(alg, sk, "", "signed message"); + * ``` + * * @param alg The signing algorithm to use. - * @param secretKey The secret key to use for signing. The key must be valid for use with the requested algorithm. - * @param contextOrHash Domain-Separator Context, or precomputed hash bytes - * @param message Message to sign, should be zero-length if precomputed hash given + * @param secretKey The secret key to use for signing. The key must be valid + * for use with the requested algorithm. + * @param contextOrHash Domain-Separator Context, or precomputed hash bytes. + * @param message Message to sign, should be zero-length if precomputed hash + * given. * @return signature The resulting signature. * @custom:see @oasisprotocol/oasis-sdk :: precompile/confidential.rs :: call_sign */ @@ -207,11 +426,48 @@ library Sapphire { } /** - * @dev Verifies that the provided digest was signed with using the secret key corresponding to the provided private key and the specified signing algorithm. + * @notice Verifies that the provided digest was signed with using the + * secret key corresponding to the provided private key and the specified + * signing algorithm. + * + * The `method`, `context_or_digest` and `message` parameters have the same + * meaning as described above in the [sign()](#sign) function. + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000007` + * + * #### Gas cost + * + * The algorithm-specific base cost below, with an additional **8 gas per + * 32 bytes** of `context` and `message` for the `Ed25519Oasis`, + * `Ed25519Pure` and `Secp256k1Oasis` algorithms. + * + * - `0` (`Ed25519Oasis`): 2,000 gas + * - `1` (`Ed25519Pure`): 2,000 gas + * - `2` (`Ed25519PrehashedSha512`): 2,000 gas + * - `3` (`Secp256k1Oasis`): 3,000 gas + * - `4` (`Secp256k1PrehashedKeccak256`): 3,000 gas + * - `5` (`Secp256k1PrehashedSha256`): 3,000 gas + * - `7` (`Secp256r1PrehashedSha256`): 7,900 gas + * + * #### Example + * + * ```solidity + * Sapphire.SigningAlg alg = Sapphire.SigningAlg.Secp256k1PrehashedKeccak256; + * bytes memory pk; + * bytes memory sk; + * bytes memory digest = abi.encodePacked(keccak256("signed message")); + * (pk, sk) = Sapphire.generateSigningKeyPair(alg, Sapphire.randomBytes(32, "")); + * bytes memory signature = Sapphire.sign(alg, sk, digest, ""); + * require( Sapphire.verify(alg, pk, digest, "", signature) ); + * ``` + * * @param alg The signing algorithm by which the signature was generated. * @param publicKey The public key against which to check the signature. * @param contextOrHash Domain-Separator Context, or precomputed hash bytes - * @param message The hash of the message that was signed, should be zero-length if precomputed hash was given + * @param message The hash of the message that was signed, should be + * zero-length if precomputed hash was given. * @param signature The signature to check. * @return verified Whether the signature is valid for the given parameters. * @custom:see @oasisprotocol/oasis-sdk :: precompile/confidential.rs :: call_verify @@ -231,11 +487,11 @@ library Sapphire { } /** - * @dev Set the current transactions gas usage to a specific amount + * @notice Set the current transactions gas usage to a specific amount + * @dev Will cause a reversion if the current usage is more than the amount. * @param toAmount Gas usage will be set to this amount * @custom:see @oasisprotocol/oasis-sdk :: precompile/gas.rs :: call_pad_gas * - * Will cause a reversion if the current usage is more than the amount */ function padGas(uint128 toAmount) internal view { (bool success, ) = PAD_GAS.staticcall(abi.encode(toAmount)); @@ -243,7 +499,7 @@ library Sapphire { } /** - * @dev Returns the amount of gas currently used by the transaction + * @notice Returns the amount of gas currently used by the transaction * @custom:see @oasisprotocol/oasis-sdk :: precompile/gas.rs :: call_gas_used */ function gasUsed() internal view returns (uint64) { @@ -254,16 +510,35 @@ library Sapphire { } /** - * Hash the input data with SHA-512/256 + * @notice Hash the input data with SHA-512/256, according to + * [NIST.FIPS.180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf). + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000102` + * + * #### Gas cost + * + * 115 gas, then 13 gas per word + * + * #### Example + * + * ```solidity + * bytes32 result = sha512_256(abi.encodePacked("input data")); + * ``` * - * SHA-512 is vulnerable to length-extension attacks, which are relevant if you - * are computing the hash of a secret message. The SHA-512/256 variant is - * **not** vulnerable to length-extension attacks. + * #### Warning: SHA-512 vs SHA-512/256 Length-Extension Attacks * + * [SHA-512](function.sha512.md#sha512) is vulnerable to [length-extension + * attacks](https://en.wikipedia.org/wiki/Length_extension_attack), which are + * relevant if you are computing the hash of a secret message. The + * [SHA-512/256](function.sha512_256.md#sha512_256) variant is **not** + * vulnerable to length-extension attacks. + * + * @param input Bytes to hash. + * @return result 32 byte digest. * @custom:standard https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf * @custom:see @oasisprotocol/oasis-sdk :: precompile/sha2.rs :: call_sha512_256 - * @param input Bytes to hash - * @return result 32 byte digest */ function sha512_256(bytes memory input) view returns (bytes32 result) { (bool success, bytes memory output) = Sapphire.SHA512_256.staticcall(input); @@ -274,12 +549,35 @@ function sha512_256(bytes memory input) view returns (bytes32 result) { } /** - * Hash the input data with SHA-512 + * @notice Hash the input data with SHA-512, according to + * [NIST.FIPS.180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) + * + * #### Precompile address + * + * `0x0100000000000000000000000000000000000101` + * + * #### Warning: SHA-512 vs SHA-512/256 Length-Extension Attacks + * + * [SHA-512](function.sha512.md#sha512) is vulnerable to [length-extension + * attacks](https://en.wikipedia.org/wiki/Length_extension_attack), which are + * relevant if you are computing the hash of a secret message. The + * [SHA-512/256](function.sha512_256.md#sha512_256) variant is **not** + * vulnerable to length-extension attacks. * + * #### Gas Cost + * + * 115 gas, then 13 gas per word + * + * #### Example + * + * ```solidity + * bytes memory result = sha512(abi.encodePacked("input data")); + * ``` + * + * @param input Bytes to hash. + * @return output 64 byte digest. * @custom:standard https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf * @custom:see @oasisprotocol/oasis-sdk :: precompile/sha2.rs :: call_sha512 - * @param input Bytes to hash - * @return output 64 byte digest */ function sha512(bytes memory input) view returns (bytes memory output) { bool success; @@ -290,12 +588,11 @@ function sha512(bytes memory input) view returns (bytes memory output) { } /** - * Hash the input data with SHA-384 - * + * @notice Hash the input data with SHA-384. + * @param input Bytes to hash. + * @return output 48 byte digest. * @custom:standard https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf * @custom:see @oasisprotocol/oasis-sdk :: precompile/sha2.rs :: call_sha384 - * @param input Bytes to hash - * @return output 48 byte digest */ function sha384(bytes memory input) view returns (bytes memory output) { bool success; diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 45d3ac13..a87f2a77 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -13,7 +13,7 @@ enum SubcallReceiptKind { /** * @title SDK Subcall wrappers - * @dev Interact with Oasis Runtime SDK modules from Sapphire. + * @notice Interact with Oasis Runtime SDK modules from Sapphire. */ library Subcall { string private constant CONSENSUS_DELEGATE = "consensus.Delegate"; @@ -64,14 +64,12 @@ library Subcall { error MissingKey(); /** - * Submit a native message to the Oasis runtime layer. - * - * Messages which re-enter the EVM module are forbidden: evm.* - * - * @param method Native message type - * @param body CBOR encoded body - * @return status Result of call - * @return data CBOR encoded result + * @notice Submit a native message to the Oasis runtime layer. Messages + * which re-enter the EVM module are forbidden: `evm.*`. + * @param method Native message type. + * @param body CBOR encoded body. + * @return status Result of call. + * @return data CBOR encoded result. */ function subcall(string memory method, bytes memory body) internal @@ -89,12 +87,12 @@ library Subcall { } /** - * @dev Generic method to call `{to:address, amount:uint128}` - * @param method Runtime SDK method name ('module.Action') - * @param to Destination address - * @param value Amount specified - * @return status Non-zero on error - * @return data Module name on error + * @notice Generic method to call `{to:address, amount:uint128}`. + * @param method Runtime SDK method name ('module.Action'). + * @param to Destination address. + * @param value Amount specified. + * @return status Non-zero on error. + * @return data Module name on error. */ function _subcallWithToAndAmount( string memory method, @@ -123,20 +121,22 @@ library Subcall { } /** - * Returns a CBOR encoded structure, containing the following possible keys. - * All keys are optional: + * @notice Returns a CBOR encoded structure, containing the following + * possible keys. All keys are optional: + * + * - shares: `u128` + * - epoch: `EpochTime` + * - receipt: `u64` + * - amount: `u128` + * - error: `{module: string, code: u32}` * - * shares: u128 - * epoch: EpochTime - * receipt: u64 - * amount: u128 - * error: {module: string, code: u32} + * #### Keys returned by specific subcalls * - * Delegate will have the `error` or `shares` keys. - * UndelegateStart will have the `epoch` and `receipt` keys. - * UndelegateDone will have the `amount` key + * - `Delegate` will have the `error` or `shares` keys. + * - `UndelegateStart` will have the `epoch` and `receipt` keys. + * - `UndelegateDone` will have the `amount` key. * - * @param kind 1=Delegate, 2=UndelegateStart, 3=UndelegateDone + * @param kind `1` (`Delegate`), `2` (`UndelegateStart`) or `3` (`UndelegateDone`) * @param receiptId ID of receipt */ function consensusTakeReceipt(SubcallReceiptKind kind, uint64 receiptId) @@ -306,9 +306,9 @@ library Subcall { } /** - * Decodes a 'Delegate' receipt - * @param receiptId Previously unretrieved receipt - * @param result CBOR encoded {shares: u128} + * @notice Decodes a 'Delegate' receipt. + * @param receiptId Previously unretrieved receipt. + * @param result CBOR encoded {shares: u128}. */ function _decodeReceiptDelegate(uint64 receiptId, bytes memory result) internal @@ -370,11 +370,10 @@ library Subcall { } /** - * Start the undelegation process of the given number of shares from + * @notice Start the undelegation process of the given number of shares from * consensus staking account to runtime account. - * - * @param from Consensus address which shares were delegated to - * @param shares Number of shares to withdraw back to us + * @param from Consensus address which shares were delegated to. + * @param shares Number of shares to withdraw back to us. */ function consensusUndelegate(StakingAddress from, uint128 shares) internal { (uint64 status, bytes memory data) = subcall( @@ -435,10 +434,9 @@ library Subcall { } /** - * Delegate native token to consensus level. - * - * @param to Consensus address shares are delegated to - * @param amount Native token amount (in wei) + * @notice Delegate native token to consensus level. + * @param to Consensus address shares are delegated to. + * @param amount Native token amount (in wei). */ function consensusDelegate(StakingAddress to, uint128 amount) internal @@ -459,15 +457,13 @@ library Subcall { } /** - * Delegate native token to consensus level. - * - * Requests that the number of shares allocated can be retrieved with a - * receipt. The receipt will be of `ReceiptKind.DelegateDone` and can be - * decoded using `decodeReceiptDelegateDone` - * - * @param to Consensus address shares are delegated to - * @param amount Native token amount (in wei) - * @param receiptId contract-specific receipt to retrieve result + * @notice Delegate native token to consensus level. Requests that the + * number of shares allocated can be retrieved with a receipt. The receipt + * will be of `ReceiptKind.DelegateDone` and can be decoded using + * `decodeReceiptDelegateDone`. + * @param to Consensus address shares are delegated to. + * @param amount Native token amount (in wei). + * @param receiptId contract-specific receipt to retrieve result. */ function consensusDelegate( StakingAddress to, @@ -510,10 +506,10 @@ library Subcall { } /** - * Transfer from an account in this runtime to a consensus staking account. - * - * @param to Consensus address which gets the tokens - * @param value Token amount (in wei) + * @notice Transfer from an account in this runtime to a consensus staking + * account. + * @param to Consensus address which gets the tokens. + * @param value Token amount (in wei). */ function consensusWithdraw(StakingAddress to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( @@ -529,12 +525,10 @@ library Subcall { } /** - * Perform a transfer to another account. - * - * This is equivalent of `payable(to).transfer(value);` - * - * @param to Destination account - * @param value native token amount (in wei) + * @notice Perform a transfer to another account. This is equivalent of + * `payable(to).transfer(value);`. + * @param to Destination account. + * @param value native token amount (in wei). */ function accountsTransfer(address to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( diff --git a/contracts/contracts/opl/Enclave.sol b/contracts/contracts/opl/Enclave.sol index c6913ff7..106f03e4 100644 --- a/contracts/contracts/opl/Enclave.sol +++ b/contracts/contracts/opl/Enclave.sol @@ -5,7 +5,7 @@ import {Endpoint} from "./Endpoint.sol"; /** * @title OPL Enclave - * @dev The Sapphire-side of an OPL dapp. + * @notice The Sapphire-side of an OPL dApp. */ contract Enclave is Endpoint { constructor(address _host, bytes32 _hostChain) diff --git a/contracts/contracts/opl/Endpoint.sol b/contracts/contracts/opl/Endpoint.sol index 9be0f744..e5ae6a91 100644 --- a/contracts/contracts/opl/Endpoint.sol +++ b/contracts/contracts/opl/Endpoint.sol @@ -160,7 +160,7 @@ contract BaseEndpoint is Context { /** * @title OPL Endpoint - * @dev An app that sends or receives using OPL. + * @notice An app that sends or receives using OPL. */ contract Endpoint is BaseEndpoint { constructor(address _remote, bytes32 _remoteChainName) @@ -176,9 +176,12 @@ contract Endpoint is BaseEndpoint { /* solhint-disable func-visibility */ /** - * @dev Autoswitch automatically picks the remote network based on the network the contract on which the contract has already been deployed. - * @dev When on testnet, the remote chain will be the testnet version of the provided chain. - * @dev When running locally, the remote chain will be this one and the contracts will call each other without going through a message bus. This is helpful for debugging logic but does not test gas fee payment and other moving parts. + * @notice Autoswitch automatically picks the remote network based on the + * network the contract on which the contract has already been deployed. When on + * testnet, the remote chain will be the testnet version of the provided chain. + * When running locally, the remote chain will be this one and the contracts + * will call each other without going through a message bus. This is helpful for + * debugging logic but does not test gas fee payment and other moving parts. */ function autoswitch(bytes32 protocol) view returns (bytes32 networkName) { if (block.chainid == 1337 || block.chainid == 31337) return "local"; diff --git a/contracts/contracts/opl/Host.sol b/contracts/contracts/opl/Host.sol index b333c2e4..0740a948 100644 --- a/contracts/contracts/opl/Host.sol +++ b/contracts/contracts/opl/Host.sol @@ -5,7 +5,7 @@ import {Endpoint, autoswitch} from "./Endpoint.sol"; /** * @title OPL Host - * @dev The L1-side of an OPL dapp. + * @notice The L1-side of an OPL dApp. */ contract Host is Endpoint { // solhint-disable-next-line no-empty-blocks diff --git a/contracts/package.json b/contracts/package.json index 1fa432a0..b895bb21 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -9,6 +9,7 @@ "url": "https://github.com/oasisprotocol/sapphire-paratime.git" }, "scripts": { + "doc": "forge doc --build && ./post-build-doc.sh", "lint:eslint": "eslint --ignore-path .gitignore --ext .ts", "lint:solhint": "solhint 'contracts/**/*.sol'", "lint::prettier": "prettier --cache --check --plugin-search-dir=. --cache '*.json' '**/*.ts' '**/*.sol'", diff --git a/contracts/post-build-doc.sh b/contracts/post-build-doc.sh new file mode 100755 index 00000000..944f9beb --- /dev/null +++ b/contracts/post-build-doc.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# This script is to be executed by `pnpm doc` after building the docs. + +# Inject another /contracts/ for github.com URLs. +find sol/sapphire-contracts/book -name *.html | xargs sed -i -E "s+(blob/.*/contracts)+\1/contracts+" + +# Remove /src/ from "Inherits" links. +find sol/sapphire-contracts/book -name *.html | xargs sed -i "s+/src/+/+" + +# Inject nicer Pagetoc theme (hides level-4 headings, smaller font, wider toc). +cp theme/* sol/sapphire-contracts/book/theme diff --git a/contracts/theme/pagetoc.css b/contracts/theme/pagetoc.css new file mode 100644 index 00000000..fc3d8d31 --- /dev/null +++ b/contracts/theme/pagetoc.css @@ -0,0 +1,58 @@ +@media only screen and (max-width:1439px) { + .sidetoc { + display: none; + } +} + +@media only screen and (min-width:1440px) { + main { + position: relative; + } + .sidetoc { + margin-left: auto; + margin-right: auto; + left: calc(100% + (var(--content-max-width))/4 - 140px); + position: absolute; + } + .pagetoc { + font-size: 12px; + position: fixed; + width: 220px; + height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); + overflow: auto; + } + .pagetoc a { + border-left: 1px solid var(--sidebar-bg); + color: var(--fg) !important; + display: block; + padding-bottom: 5px; + padding-top: 5px; + padding-left: 10px; + text-align: left; + text-decoration: none; + } + .pagetoc a:hover, + .pagetoc a.active { + background: var(--sidebar-bg); + color: var(--sidebar-fg) !important; + } + .pagetoc .active { + background: var(--sidebar-bg); + color: var(--sidebar-fg); + } + .pagetoc .pagetoc-H2 { + padding-left: 20px; + } + .pagetoc .pagetoc-H3 { + padding-left: 40px; + } + .pagetoc .pagetoc-H4 { + display: none; + } + .pagetoc .pagetoc-H5 { + display: none; + } + .pagetoc .pagetoc-H6 { + display: none; + } +} diff --git a/docs/precompiles.md b/docs/precompiles.md deleted file mode 100644 index 1c09d2c2..00000000 --- a/docs/precompiles.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -description: Additional Sapphire precompiles for encryption and confidentiality ---- - -# Precompiles - -In addition to the standard EVM precompiles, Sapphire provides a number -of further cryptography-related ones to make some operations easier and -cheaper to perform: x25519 key derivation, Deoxys-II-based encryption -and decryption, signing key generation, message digest signing and -verification. - -These can be called in the same way as other precompiles by dispatching -calls to specific well-known contract addresses, as described below. - -Input parameters should be packed into a contiguous memory region with -each chunk of data padded to 32 bytes as usual. The recommended way to -construct parameter byte sequences in Solidity is with `abi.encode` and -`abi.decode`, which will transparently handle things like putting -`bytes` lengths in the correct position. - -## Library - -While it is possible to call the precompiles directly using Yul or, for -example, `abi.encode` and `abi.decode` in Solidity, we recommend always -using the `contracts/Sapphire.sol` wrapper library for a more comfortable -experience. The examples below are written against it. The library is provided -by the `@oasisprotocol/sapphire-contracts` npm package. - -```shell npm2yarn -npm install -D @oasisprotocol/sapphire-contracts -``` - -Then, you can use the wrapper library inside your `.sol` contract file as -follows: - -```solidity -pragma solidity ^0.8.13; - -import "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol"; - -contract Test { - constructor() {} - function test() public view returns (bytes32) { - return Sapphire.deriveSymmetricKey("public key as bytes32", "private key as bytes32"); - } -} -``` - -Feel free to discover other convenient libraries for Solidity inside the -`contracts/` folder of the -[Oasis Sapphire repository](https://github.com/oasisprotocol/sapphire-paratime)! - -## Generating Pseudo-Random Bytes - -* Precompile address: `0x0100000000000000000000000000000000000001` -* Parameters: `uint num_bytes, bytes pers` -* Gas cost: 10,000 minimum plus 240 per output word plus 60 per word of - the personalization string. - -Generate `num_bytes` pseudo-random bytes, with an optional personalization -string (`pers`) added into the hashing algorithm to increase domain separation -when needed. - -```solidity -bytes memory randomPad = Sapphire.randomBytes(64, ""); -``` - -### Implementation Details - -:::danger Prior to 0.6.0 -All view queries and simulated transactions (via `eth_call`) would receive the -same entropy in-between blocks if they use the same `num_bytes` and `pers` parameters. -If your contract requires confidentiality you should generate a secret in the constructor -to be used with view calls: - -```solidity -Sapphire.randomBytes(64, abi.encodePacked(msg.sender, this.perContactSecret)); -``` -::: - -The mode (e.g. simulation or 'view call' vs transaction execution) is fed to TupleHash (among other -block-dependent components) to derive the "key id", which is then used to derive a per-block VRF key -from epoch-ephemeral entropy (using KMAC256 and cSHAKE) so a different "key id" will result in a -unique per-block VRF key. This per-block VRF key is then used to create the per-block root RNG which -is then used to derive domain-separated (using Merlin transcripts) per-transaction random RNGs which -are then exposed via this precompile. The KMAC, cSHAKE and TupleHash algorithms are SHA-3 derived functions -defined in [NIST Special Publication 800-185](https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf). - -## X25519 Key Derivation - -* Precompile address: `0x0100000000000000000000000000000000000002` -* Parameters: `bytes32 public_key, bytes32 private_key` -* Gas cost: 100,000 - -### Example - -```solidity -bytes32 publicKey = ... ; -bytes32 privateKey = ... ; -bytes32 symmetric = Sapphire.deriveSymmetricKey(publicKey, privateKey); -``` - -## Deoxys-II Encryption - -* Encryption precompile address: `0x0100000000000000000000000000000000000003` -* Decryption precompile address: `0x0100000000000000000000000000000000000004` -* Parameters: `bytes32 key, bytes32 nonce, bytes text_or_ciphertext, bytes additional_data` -* Gas cost: 50,000 minimum plus 100 per word of input - -### Example - -```solidity -bytes32 key = ... ; -bytes32 nonce = ... ; -bytes memory text = "plain text"; -bytes memory ad = "additional data"; -bytes memory encrypted = Sapphire.encrypt(key, nonce, text, ad); -bytes memory decrypted = Sapphire.decrypt(key, nonce, encrypted, ad); -``` - -## Signing Keypairs Generation - -* Precompile address: `0x0100000000000000000000000000000000000005` -* Parameters: `uint method, bytes seed` -* Return value: `bytes public_key, bytes private_key` -* Gas cost: method-dependent base cost, see below - -The available methods are items in the `Sapphire.SigningAlg` enum. Note, -however, that the generation method ignores subvariants, so all three -ed25519-based are equivalent, and all secp256k1 & secp256r1 based methods are -equivalent. `Sr25519` is not available and will return an error. - -### Gas Cost -* Ed25519: 1,000 gas - * `0` (`Ed25519Oasis`) - * `1` (`Ed25519Pure`) - * `2` (`Ed25519PrehashedSha512`) -* Secp256k1: 1,500 gas. - * `3` (`Secp256k1Oasis`) - * `4` (`Secp256k1PrehashedKeccak256`) - * `5` (`Secp256k1PrehashedSha256`) -* Secp256r1: 4,000 gas - * `7` (`Secp256r1PrehashedSha256`) - -### Public Key Format - - * Ed25519: 32 bytes - * Secp256k1 & Secp256r1: 33 bytes, compressed format (0x02 or 0x03 prefix, then 32 byte X coordinate) - -### Example - -Using the Sapphire library: - -```solidity -bytes memory seed = hex"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; -bytes memory publicKey; -bytes memory privateKey; -(publicKey, privateKey) = Sapphire.generateSigningKeyPair(Sapphire.SigningAlg.Ed25519Pure, seed); -``` - -## Message Signing - -* Precompile address: `0x0100000000000000000000000000000000000006` -* Parameters: `uint method, bytes private_key, bytes context_or_digest, bytes message` -* Gas cost: see below for the method-dependent base cost, plus 8 gas per 32 bytes of context and message except digest. - -The `context_or_digest` and `messages` parameters change in meaning slightly -depending on the method requested. For methods that take a context in addition -to the message you must pass the context in the `context_or_digest` parameter -and use `message` as expected. For methods that take a pre-existing hash of the -message, pass that in `context_or_digest` and leave `message` empty. -Specifically the `Ed25519Oasis` and `Secp256k1Oasis` variants take both a -context and a message (each are variable length `bytes`), the context serves as -a domain separator. - -### Signing Algorithms - -* `0` (`Ed25519Oasis`) - * 1,500 gas - * variable length context and message -* `1` (`Ed25519Pure`) - * 1,500 gas - * empty context, variable length message -* `2` (`Ed25519PrehashedSha512`) - * 1,500 gas - * pre-existing SHA-512 hash (64 bytes) as context, empty message -* `3` (`Secp256k1Oasis`) - * 3,000 gas - * variable length context and message -* `4` (`Secp256k1PrehashedKeccak256`) - * 3,000 gas - * pre-existing hash (32 bytes) as context, empty message -* `5` (`Secp256k1PrehashedSha256`) - * 3,000 gas - * pre-existing hash (32 bytes) as context, empty message -* `7` (`Secp256r1PrehashedSha256`) - * 9,000 gas - * pre-existing hash (32 bytes) as context, empty message - -### Example - -Using the Sapphire library: - -```solidity -Sapphire.SigningAlg alg = Sapphire.SigningAlg.Ed25519Pure; -bytes memory pk; -bytes memory sk; -(pk, sk) = Sapphire.generateSigningKeyPair(alg, Sapphire.randomBytes(32, "")); -bytes memory signature = Sapphire.sign(alg, sk, "", "signed message"); -``` - -## Signature Verification - -* Precompile address: `0x0100000000000000000000000000000000000007` -* Parameters: `uint method, bytes public_key, bytes context_or_digest, bytes message, bytes signature` - -The `method`, `context_or_digest` and `message` parameters have the same meaning -as described above in the Message Signing section. - -### Gas Cost - -The algorithm-specific base cost below, with an additional 8 gas per 32 bytes of -`context` and `message` for the `Ed25519Oasis`, `Ed25519Pure` and `Secp256k1Oasis` algorithms. - -* Ed25519: 2,000 gas - * `0` (`Ed25519Oasis`) - * `1` (`Ed25519Pure`) - * `2` (`Ed25519PrehashedSha512`) -* Secp256k1: 3,000 gas - * `3` (`Secp256k1Oasis`) - * `4` (`Secp256k1PrehashedKeccak256`) - * `5` (`Secp256k1PrehashedSha256`) -* Secp256r1: 7,900 gas - * `7` (`Secp256r1PrehashedSha256`) - -### Example - -Using the Sapphire library: - -```solidity -Sapphire.SigningAlg alg = Sapphire.SigningAlg.Secp256k1PrehashedKeccak256; -bytes memory pk; -bytes memory sk; -bytes memory digest = abi.encodePacked(keccak256("signed message")); -(pk, sk) = Sapphire.generateSigningKeyPair(alg, Sapphire.randomBytes(32, "")); -bytes memory signature = Sapphire.sign(alg, sk, digest, ""); -require( Sapphire.verify(alg, pk, digest, "", signature) ); -``` - -## SHA-512 - - * Precompile address: `0x0100000000000000000000000000000000000101` - * Parameters: `bytes input_data` - -Hash the input data with SHA-512, according to [NIST.FIPS.180-4] - -:::warning SHA-512 is vulnerable to length-extension attacks - -The SHA-512/256 variant (below) is not vulnerable to [length-extension attacks]. -Length extension attacks are relevant if, among other things, you are computing -the hash of a secret message or computing merkle trees. - -::: - -[length-extension attacks]: https://en.wikipedia.org/wiki/Length_extension_attack - -### Gas Cost - -* 115 gas, then 13 gas per word - -### Example - -```solidity -bytes memory result = sha512(abi.encodePacked("input data")); -``` - - -## SHA-512/256 - - * Precompile address: `0x0100000000000000000000000000000000000102` - * Parameters: `bytes input_data` - -Hash the input data with SHA-512/256, according to [NIST.FIPS.180-4] - -[NIST.FIPS.180-4]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf - -### Gas Cost - - * 115 gas, then 13 gas per word - -### Example - -```solidity -bytes32 result = sha512_256(abi.encodePacked("input data")); -``` - - -## Subcall - - * Precompile address: `0x0100000000000000000000000000000000000102` - * Parameters: `string method, bytes cborEncodedParams` - -Subcall performs an Oasis SDK call. This allows Sapphire contracts to interact -with the Consensus layer and other modules supported by the SDK. For more -information about the specific modules and their available calls see the Oasis -SDK [source code]. - -### Gas Cost - -Varies per operation, refer to the oasis-sdk [source code]. - -### Example - -TODO: an example - -[source code]: https://github.com/oasisprotocol/oasis-sdk/tree/main/runtime-sdk/src/modules