-
Notifications
You must be signed in to change notification settings - Fork 20
/
Sapphire.sol
603 lines (580 loc) · 22.3 KB
/
Sapphire.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/**
* @title Sapphire
* @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
address internal constant RANDOM_BYTES =
0x0100000000000000000000000000000000000001;
address internal constant DERIVE_KEY =
0x0100000000000000000000000000000000000002;
address internal constant ENCRYPT =
0x0100000000000000000000000000000000000003;
address internal constant DECRYPT =
0x0100000000000000000000000000000000000004;
address internal constant GENERATE_SIGNING_KEYPAIR =
0x0100000000000000000000000000000000000005;
address internal constant SIGN_DIGEST =
0x0100000000000000000000000000000000000006;
address internal constant VERIFY_DIGEST =
0x0100000000000000000000000000000000000007;
address internal constant CURVE25519_PUBLIC_KEY =
0x0100000000000000000000000000000000000008;
address internal constant GAS_USED =
0x0100000000000000000000000000000000000009;
address internal constant PAD_GAS =
0x010000000000000000000000000000000000000a;
// Oasis-specific, general precompiles
address internal constant SHA512_256 =
0x0100000000000000000000000000000000000101;
address internal constant SHA512 =
0x0100000000000000000000000000000000000102;
address internal constant SHA384 =
0x0100000000000000000000000000000000000104;
type Curve25519PublicKey is bytes32;
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.
Ed25519Oasis,
/// Ed25519 signature over the provided message.
Ed25519Pure,
/// 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.
Secp256k1Oasis,
/// 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.
Secp256k1PrehashedSha256,
/// Sr25519 signature over the provided message.
Sr25519,
/// Secp256r1 signature over the provided SHA-256 digest.
Secp256r1PrehashedSha256,
/// Secp384r1 signature over the provided SHA-384 digest.
Secp384r1PrehashedSha384
}
/**
* @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.
*/
function randomBytes(uint256 numBytes, bytes memory pers)
internal
view
returns (bytes memory)
{
(bool success, bytes memory entropy) = RANDOM_BYTES.staticcall(
abi.encode(numBytes, pers)
);
require(success, "randomBytes: failed");
return entropy;
}
/**
* @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](#derivesymmetrickey).
*/
function generateCurve25519KeyPair(bytes memory pers)
internal
view
returns (Curve25519PublicKey pk, Curve25519SecretKey sk)
{
bytes memory scalar = randomBytes(32, pers);
// Twiddle some bits, as per RFC 7748 §5.
scalar[0] &= 0xf8; // Make it a multiple of 8 to avoid small subgroup attacks.
scalar[31] &= 0x7f; // Clamp to < 2^255 - 19
scalar[31] |= 0x40; // Clamp to >= 2^254
(bool success, bytes memory pkBytes) = CURVE25519_PUBLIC_KEY.staticcall(
scalar
);
require(success, "gen curve25519 pk: failed");
return (
Curve25519PublicKey.wrap(bytes32(pkBytes)),
Curve25519SecretKey.wrap(bytes32(scalar))
);
}
/**
* @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.
*/
function deriveSymmetricKey(
Curve25519PublicKey peerPublicKey,
Curve25519SecretKey secretKey
) internal view returns (bytes32) {
(bool success, bytes memory symmetric) = DERIVE_KEY.staticcall(
abi.encode(peerPublicKey, secretKey)
);
require(success, "deriveSymmetricKey: failed");
return bytes32(symmetric);
}
/**
* @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 plaintext The plaintext to encrypt and authenticate.
* @param additionalData The additional data to authenticate.
* @return The ciphertext with appended auth tag.
*/
function encrypt(
bytes32 key,
bytes32 nonce,
bytes memory plaintext,
bytes memory additionalData
) internal view returns (bytes memory) {
(bool success, bytes memory ciphertext) = ENCRYPT.staticcall(
abi.encode(key, nonce, plaintext, additionalData)
);
require(success, "encrypt: failed");
return ciphertext;
}
/**
* @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 ciphertext The ciphertext with tag to decrypt and authenticate.
* @param additionalData The additional data to authenticate against the
* ciphertext.
* @return The original plaintext.
*/
function decrypt(
bytes32 key,
bytes32 nonce,
bytes memory ciphertext,
bytes memory additionalData
) internal view returns (bytes memory) {
(bool success, bytes memory plaintext) = DECRYPT.staticcall(
abi.encode(key, nonce, ciphertext, additionalData)
);
require(success, "decrypt: failed");
return plaintext;
}
/**
* @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.
* @return publicKey The public half of the keypair.
* @return secretKey The secret half of the keypair.
*/
function generateSigningKeyPair(SigningAlg alg, bytes memory seed)
internal
view
returns (bytes memory publicKey, bytes memory secretKey)
{
(bool success, bytes memory keypair) = GENERATE_SIGNING_KEYPAIR
.staticcall(abi.encode(alg, seed));
require(success, "gen signing keypair: failed");
return abi.decode(keypair, (bytes, bytes));
}
/**
* @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.
* @return signature The resulting signature.
* @custom: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) {
(bool success, bytes memory sig) = SIGN_DIGEST.staticcall(
abi.encode(alg, secretKey, contextOrHash, message)
);
require(success, "sign: failed");
return sig;
}
/**
* @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 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
*/
function verify(
SigningAlg alg,
bytes memory publicKey,
bytes memory contextOrHash,
bytes memory message,
bytes memory signature
) internal view returns (bool verified) {
(bool success, bytes memory v) = VERIFY_DIGEST.staticcall(
abi.encode(alg, publicKey, contextOrHash, message, signature)
);
require(success, "verify: failed");
return abi.decode(v, (bool));
}
/**
* @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
*
*/
function padGas(uint128 toAmount) internal view {
(bool success, ) = PAD_GAS.staticcall(abi.encode(toAmount));
require(success, "verify: failed");
}
/**
* @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) {
(bool success, bytes memory v) = GAS_USED.staticcall("");
require(success, "gasused: failed");
return abi.decode(v, (uint64));
}
}
/**
* @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"));
* ```
*
* #### 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
*/
function sha512_256(bytes memory input) view returns (bytes32 result) {
(bool success, bytes memory output) = Sapphire.SHA512_256.staticcall(input);
require(success, "sha512_256");
return bytes32(output);
}
/**
* @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
*/
function sha512(bytes memory input) view returns (bytes memory output) {
bool success;
(success, output) = Sapphire.SHA512.staticcall(input);
require(success, "sha512");
}
/**
* @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
*/
function sha384(bytes memory input) view returns (bytes memory output) {
bool success;
(success, output) = Sapphire.SHA384.staticcall(input);
require(success, "sha384");
}