Sapphire
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.
State Variables
RANDOM_BYTES
address internal constant RANDOM_BYTES = 0x0100000000000000000000000000000000000001;
DERIVE_KEY
address internal constant DERIVE_KEY = 0x0100000000000000000000000000000000000002;
ENCRYPT
address internal constant ENCRYPT = 0x0100000000000000000000000000000000000003;
DECRYPT
address internal constant DECRYPT = 0x0100000000000000000000000000000000000004;
GENERATE_SIGNING_KEYPAIR
address internal constant GENERATE_SIGNING_KEYPAIR = 0x0100000000000000000000000000000000000005;
SIGN_DIGEST
address internal constant SIGN_DIGEST = 0x0100000000000000000000000000000000000006;
VERIFY_DIGEST
address internal constant VERIFY_DIGEST = 0x0100000000000000000000000000000000000007;
CURVE25519_PUBLIC_KEY
address internal constant CURVE25519_PUBLIC_KEY = 0x0100000000000000000000000000000000000008;
GAS_USED
address internal constant GAS_USED = 0x0100000000000000000000000000000000000009;
PAD_GAS
address internal constant PAD_GAS = 0x010000000000000000000000000000000000000a;
SHA512_256
address internal constant SHA512_256 = 0x0100000000000000000000000000000000000101;
SHA512
address internal constant SHA512 = 0x0100000000000000000000000000000000000102;
SHA384
address internal constant SHA384 = 0x0100000000000000000000000000000000000104;
Functions
randomBytes
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.
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:
Sapphire.randomBytes(64, abi.encodePacked(msg.sender, this.perContactSecret));
Example
bytes memory randomPad = Sapphire.randomBytes(64, "");
function randomBytes(uint256 numBytes, bytes memory pers) internal view returns (bytes memory);
Parameters
Name | Type | Description |
---|---|---|
numBytes | uint256 | The number of bytes to return. |
pers | bytes | An optional personalization string to increase domain separation. |
Returns
Name | Type | Description |
---|---|---|
<none> | bytes | The random bytes. If the number of bytes requested is too large (over 1024), a smaller amount (1024) will be returned. |
generateCurve25519KeyPair
Generates a Curve25519 keypair.
function generateCurve25519KeyPair(bytes memory pers)
internal
view
returns (Curve25519PublicKey pk, Curve25519SecretKey sk);
Parameters
Name | Type | Description |
---|---|---|
pers | bytes | An optional personalization string used to add domain separation. |
Returns
Name | Type | Description |
---|---|---|
pk | Curve25519PublicKey | The Curve25519 public key. Useful for key exchange. |
sk | Curve25519SecretKey | The Curve25519 secret key. Pairs well with deriveSymmetricKey. |
deriveSymmetricKey
Derive a symmetric key from a pair of keys using x25519.
Precompile address
0x0100000000000000000000000000000000000002
Gas cost
100,000
Example
bytes32 publicKey = ... ;
bytes32 privateKey = ... ;
bytes32 symmetric = Sapphire.deriveSymmetricKey(publicKey, privateKey);
function deriveSymmetricKey(Curve25519PublicKey peerPublicKey, Curve25519SecretKey secretKey)
internal
view
returns (bytes32);
Parameters
Name | Type | Description |
---|---|---|
peerPublicKey | Curve25519PublicKey | The peer's public key. |
secretKey | Curve25519SecretKey | Your secret key. |
Returns
Name | Type | Description |
---|---|---|
<none> | bytes32 | A derived symmetric key. |
encrypt
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
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);
function encrypt(bytes32 key, bytes32 nonce, bytes memory plaintext, bytes memory additionalData)
internal
view
returns (bytes memory);
Parameters
Name | Type | Description |
---|---|---|
key | bytes32 | The key to use for encryption. |
nonce | bytes32 | The nonce. Note that only the first 15 bytes of this parameter are used. |
plaintext | bytes | The plaintext to encrypt and authenticate. |
additionalData | bytes | The additional data to authenticate. |
Returns
Name | Type | Description |
---|---|---|
<none> | bytes | The ciphertext with appended auth tag. |
decrypt
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
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);
function decrypt(bytes32 key, bytes32 nonce, bytes memory ciphertext, bytes memory additionalData)
internal
view
returns (bytes memory);
Parameters
Name | Type | Description |
---|---|---|
key | bytes32 | The key to use for decryption. |
nonce | bytes32 | The nonce. Note that only the first 15 bytes of this parameter are used. |
ciphertext | bytes | The ciphertext with tag to decrypt and authenticate. |
additionalData | bytes | The additional data to authenticate against the ciphertext. |
Returns
Name | Type | Description |
---|---|---|
<none> | bytes | The original plaintext. |
generateSigningKeyPair
Generate a public/private key pair using the specified method and
seed. 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.
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
)
Secp384r1: 18,000 gas
8
(Secp384r1PrehashedSha384
)
Key Formats
Ed25519
Public key: 32 bytes Secret key: 32 bytes
Secp256k1 & Secp256r1
Public key: 33 bytes, compressed format (0x02
or 0x03
prefix, then 32
byte X coordinate).
Secret key: 32 bytes
Secp384r1
Public key: 49 bytes, compressed format (0x02
or 0x03
prefix, then 48
byte X coordinate).
Secret key: 48 bytes
Example
bytes memory seed = hex"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
bytes memory publicKey;
bytes memory privateKey;
(publicKey, privateKey) = Sapphire.generateSigningKeyPair(Sapphire.SigningAlg.Ed25519Pure, seed);
function generateSigningKeyPair(SigningAlg alg, bytes memory seed)
internal
view
returns (bytes memory publicKey, bytes memory secretKey);
Parameters
Name | Type | Description |
---|---|---|
alg | SigningAlg | The signing alg for which to generate a keypair. |
seed | bytes | The seed to use for generating the key pair. You can use the randomBytes method if you don't already have a seed. |
Returns
Name | Type | Description |
---|---|---|
publicKey | bytes | The public part of the keypair. |
secretKey | bytes | The secret part of the keypair. |
sign
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 message4
(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.8
(Secp384r1PrehashedSha384
): 43,200 gas, pre-existing hash (32 bytes) as context, empty message.
Example
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");
Note: see: @oasisprotocol/oasis-sdk :: precompile/confidential.rs :: call_sign
function sign(SigningAlg alg, bytes memory secretKey, bytes memory contextOrHash, bytes memory message)
internal
view
returns (bytes memory signature);
Parameters
Name | Type | Description |
---|---|---|
alg | SigningAlg | The signing algorithm to use. |
secretKey | bytes | The secret key to use for signing. The key must be valid for use with the requested algorithm. |
contextOrHash | bytes | Domain-Separator Context, or precomputed hash bytes. |
message | bytes | Message to sign, should be zero-length if precomputed hash given. |
Returns
Name | Type | Description |
---|---|---|
signature | bytes | The resulting signature. |
verify
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() 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 gas1
(Ed25519Pure
): 2,000 gas2
(Ed25519PrehashedSha512
): 2,000 gas3
(Secp256k1Oasis
): 3,000 gas4
(Secp256k1PrehashedKeccak256
): 3,000 gas5
(Secp256k1PrehashedSha256
): 3,000 gas7
(Secp256r1PrehashedSha256
): 7,900 gas8
(Secp384r1PrehashedSha384
): 37,920 gas
Example
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) );
Note: see: @oasisprotocol/oasis-sdk :: precompile/confidential.rs :: call_verify
function verify(
SigningAlg alg,
bytes memory publicKey,
bytes memory contextOrHash,
bytes memory message,
bytes memory signature
) internal view returns (bool verified);
Parameters
Name | Type | Description |
---|---|---|
alg | SigningAlg | The signing algorithm by which the signature was generated. |
publicKey | bytes | The public key against which to check the signature. |
contextOrHash | bytes | Domain-Separator Context, or precomputed hash bytes |
message | bytes | The hash of the message that was signed, should be zero-length if precomputed hash was given. |
signature | bytes | The signature to check. |
Returns
Name | Type | Description |
---|---|---|
verified | bool | Whether the signature is valid for the given parameters. |
padGas
Set the current transactions gas usage to a specific amount
Will cause a reversion if the current usage is more than the amount.
Note: see: @oasisprotocol/oasis-sdk :: precompile/gas.rs :: call_pad_gas
function padGas(uint128 toAmount) internal view;
Parameters
Name | Type | Description |
---|---|---|
toAmount | uint128 | Gas usage will be set to this amount |
gasUsed
Returns the amount of gas currently used by the transaction
Note: see: @oasisprotocol/oasis-sdk :: precompile/gas.rs :: call_gas_used
function gasUsed() internal view returns (uint64);
Enums
SigningAlg
enum SigningAlg {
Ed25519Oasis,
Ed25519Pure,
Ed25519PrehashedSha512,
Secp256k1Oasis,
Secp256k1PrehashedKeccak256,
Secp256k1PrehashedSha256,
Sr25519,
Secp256r1PrehashedSha256,
Secp384r1PrehashedSha384
}