Skip to content

Commit

Permalink
secp256r1: Adds support for NIST's secp256r1 (p256) curve
Browse files Browse the repository at this point in the history
  • Loading branch information
pmerkleplant authored Jul 2, 2024
1 parent 1a6a4ae commit 197dcac
Show file tree
Hide file tree
Showing 29 changed files with 3,439 additions and 232 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/solc-version-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ jobs:

- name: Run forge tests against lowest and highest supported solc version
run: >
forge test --use 0.8.4 &&
forge test --use 0.8.25
forge test --use 0.8.16 &&
forge test --use 0.8.26
41 changes: 40 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ help: ## Print list of all commands
@echo ""
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-50s\033[0m %s\n", $$1, $$2}'

.PHONY: build
build: ## Build project
@forge build

.PHONY: clean
clean: ## Clean build artifacts
@forge clean

.PHONY: test
test: ## Run full test suite
@forge test
Expand All @@ -17,15 +25,46 @@ test: ## Run full test suite
test-intense: ## Run full test suite with intense fuzzing
@FOUNDRY_PROFILE=intense forge test

.PHONY: test-summary
test-summary: ## Print summary of test suite
@forge test --summary

.PHONY: coverage
coverage: ## Update coverage report and open lcov web interface
@rm -rf coverage
@forge coverage --report lcov
@genhtml --branch-coverage --output "coverage" lcov.info
@open coverage/index.html

# Note that ripgrep instead of grep is used.
# See https://github.com/BurntSushi/ripgrep.
.PHONY: todos
todos: ## Grep TODO's in src/ and test/
@rg -rn "TODO" src/ test/

.PHONY: examples
examples: ## Run examples
@echo "########################################"
@echo "##"
@echo "## Random"
@echo "##"
@echo "########################################"
@forge script examples/common/Random.sol:RandomExample -v
@echo "########################################"
@echo "##"
@echo "## Secp256k1"
@echo "##"
@echo "########################################"
@forge script examples/secp256k1/Secp256k1.sol:Secp256k1Example -v
@echo "########################################"
@echo "##"
@echo "## Secp256r1"
@echo "##"
@echo "########################################"
@forge script examples/secp256r1/Secp256r1.sol:Secp256r1Example -v

.PHONY: fmt
fmt: ## Forge fmt complete project
fmt: ## Format project
@forge fmt
@forge fmt ./examples/

20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ src
├─ onchain
│ ├─ common
│ │ ├─ Message - "Functionality for constructing Ethereum Signed Message Hashes"
│ │ └─ Nonce - "Deterministic nonce derivation"
│ └─ secp256k1
│ ├─ Secp256k1 - "Cryptography-related functionality for the secp256k1 elliptic curve"
│ ├─ Secp256k1Arithmetic — "Arithmetic-related functionality for the secp256k1 elliptic curve"
│ └─ signatures
│ ├─ ECDSA — "ECDSA signature functionality for secp256k1"
│ └─ Schnorr — "Schnorr signature functionality for secp256k1"
│ │ ├─ Nonce - "Deterministic nonce derivation"
│ │ └─ ModularArithmetic - "Provides modular arithmetic functionality"
│ ├─ secp256k1
│ │ ├─ Secp256k1 - "Cryptography-related functionality for the secp256k1 elliptic curve"
│ │ ├─ Secp256k1Arithmetic — "Arithmetic-related functionality for the secp256k1 elliptic curve"
│ │ └─ signatures
│ │ ├─ ECDSA — "ECDSA signature functionality for secp256k1"
│ │ └─ Schnorr — "Schnorr signature functionality for secp256k1"
│ └─ secp256r1
│ ├─ Secp256r1 - "Cryptography-related functionality for the secp256r1 elliptic curve"
│ └─ Secp256r1Arithmetic — "Arithmetic-related functionality for the secp256r1 elliptic curve"
├─ offchain
│ ├─ common
│ │ └─ RandomOffchain - "Access to cryptographically secure randomness"
Expand Down Expand Up @@ -89,5 +93,5 @@ We **do not give any warranties** and **will not be liable** for any loss incurr
[tests-shield]: https://github.com/verklegarden/crysol/actions/workflows/unit-tests.yml/badge.svg
[tests-shield-url]: https://github.com/verklegarden/crysol/actions/workflows/unit-tests.yml
[license-shield]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[solidity-shield]: https://img.shields.io/badge/solidity-%3E=0.8.16%20%3C=0.8.24-aa6746
[solidity-shield]: https://img.shields.io/badge/solidity-%3E=0.8.16%20%3C=0.8.26-aa6746
[solidity-shield-url]: https://github.com/verklegarden/crysol/actions/workflows/solc-version-tests.yml
7 changes: 2 additions & 5 deletions examples/common/Random.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ contract RandomExample is Script {
function run() public {
// Create random uint.
uint rand = RandomOffchain.readUint();
console.log("Random uint: ", rand);

// Bound to smaller type via discarding higher-order bits.
uint8 randByte = uint8(rand);
console.log("Random byte: ", randByte);
console.log("Cryptographically secure random uint256: ", rand);
console.log("");
}
}
79 changes: 79 additions & 0 deletions examples/secp256r1/Secp256r1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.16;

import {Script} from "forge-std/Script.sol";
import {console2 as console} from "forge-std/console2.sol";

import {Secp256r1Offchain} from "src/offchain/secp256r1/Secp256r1Offchain.sol";
import {
Secp256r1,
SecretKey,
PublicKey
} from "src/onchain/secp256r1/Secp256r1.sol";
import {
Secp256r1Arithmetic,
Point,
ProjectivePoint
} from "src/onchain/secp256r1/Secp256r1Arithmetic.sol";

/**
* @title Secp256r1Example
*
* @dev Run via:
*
* ```bash
* $ forge script examples/secp256r1/Secp256r1.sol:Secp256r1Example -vvvv
* ```
*
* @dev Note that some code is commented out to reduce compiler warnings
* regarding unused variables.
*/
contract Secp256r1Example is Script {
using Secp256r1Offchain for SecretKey;
using Secp256r1Offchain for PublicKey;
using Secp256r1 for SecretKey;
using Secp256r1 for PublicKey;
using Secp256r1Arithmetic for Point;
using Secp256r1Arithmetic for ProjectivePoint;

function run() public {
// Create new cryptographically sound secret key.
SecretKey sk = Secp256r1Offchain.newSecretKey();
// assert(sk.isValid());
console.log("Created new secret key:");
console.log(sk.asUint());
console.log("");

// Derive public key.
PublicKey memory pk = sk.toPublicKey();
// assert(pk.isValid());
console.log("Derived public key:");
console.log(pk.toString());
console.log("");

// Arithmetic types.
// into___() -> overwrites memory, no allocation / memory expansion cost
// to___() -> allocates new memory, may has expansion cost
/*
Point memory point = pk.intoPoint();
ProjectivePoint memory jPoint = pk.toProjectivePoint();
*/

// Derive common constructs.
// bytes32 digest = pk.toHash();
// uint yParity = pk.yParity();

// Serialization.
console.log("ABI serialized public key:");
console.logBytes(pk.toBytes());
console.log("");

console.log("SEC encoded public key:");
console.logBytes(pk.intoPoint().toEncoded());
console.log("");

console.log("SEC compressed encoded public key:");
console.logBytes(pk.intoPoint().toCompressedEncoded());
console.log("");
}
}
121 changes: 121 additions & 0 deletions src/offchain/secp256r1/Secp256r1Offchain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
██████ ██████  ██  ██ ███████  ██████  ██
██      ██   ██  ██  ██  ██      ██    ██ ██
██  ██████    ████   ███████ ██  ██ ██
██  ██   ██   ██        ██ ██  ██ ██
 ██████ ██  ██  ██  ███████  ██████  ███████
*/

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.16;

import {Vm} from "forge-std/Vm.sol";

import {RandomOffchain} from "../common/RandomOffchain.sol";

import {
Secp256r1,
SecretKey,
PublicKey
} from "../../onchain/secp256r1/Secp256r1.sol";
import {
Secp256r1Arithmetic,
Point,
ProjectivePoint
} from "../../onchain/secp256r1/Secp256r1Arithmetic.sol";

/**
* @title Secp256r1Offchain
*
* @notice Providing offchain cryptography-related functionality for the secp256r1
* elliptic curve
*
* @author verklegarden
* @custom:repository github.com/verklegarden/crysol
*/
library Secp256r1Offchain {
using Secp256r1Offchain for SecretKey;
using Secp256r1 for SecretKey;
using Secp256r1 for PublicKey;
using Secp256r1 for Point;
using Secp256r1Arithmetic for Point;
using Secp256r1Arithmetic for ProjectivePoint;

// ~~~~~~~ Prelude ~~~~~~~
// forgefmt: disable-start
Vm private constant vm = Vm(address(uint160(uint(keccak256("hevm cheat code")))));
modifier vmed() {
if (block.chainid != 31337) revert("requireVm");
_;
}
// forgefmt: disable-end
// ~~~~~~~~~~~~~~~~~~~~~~~

//--------------------------------------------------------------------------
// Secret Key

/// @dev Returns a new cryptographically secure secret key.
///
/// @custom:vm RandomOffchain::readUint()(uint)
function newSecretKey() internal vmed returns (SecretKey) {
// Note to not introduce potential bias via bounding operation.
uint scalar;
do {
scalar = RandomOffchain.readUint();
} while (scalar == 0 || scalar >= Secp256r1Arithmetic.Q);

return Secp256r1.secretKeyFromUint(scalar);
}

/// @dev Returns the public key of secret key `sk`.
///
/// @dev Reverts if:
/// Secret key invalid
///
/// @custom:vm vm.createWallet(uint)
function toPublicKey(SecretKey sk)
internal
view
vmed
returns (PublicKey memory)
{
if (!sk.isValid()) {
revert("SecretKeyInvalid()");
}

// TODO: Need vm support for p256 public key derivation.
// Note that this function, in its current form, does not actually
// depend on vm. However, for performance an issue in foundry will
// be created to provide vm functionality for secp256r1 public key
// derivation.
// Note that this function should for obvious reasons not be
// executed onchain anyway.
// forgefmt: disable-next-item
Point memory p = Secp256r1.G().toProjectivePoint()
.mul(sk.asUint())
.intoPoint();

return p.intoPublicKey();
}

//--------------------------------------------------------------------------
// Public Key

/// @dev Returns a string representation of public key `pk`.
///
/// @custom:vm vm.toString(uint)
function toString(PublicKey memory pk)
internal
view
vmed
returns (string memory)
{
string memory str = "Secp256r1::PublicKey({";
str = string.concat(str, " x: ", vm.toString(pk.x), ",");
str = string.concat(str, " y: ", vm.toString(pk.y));
str = string.concat(str, " })");
return str;
}
}
Loading

0 comments on commit 197dcac

Please sign in to comment.