Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stealth Address Implementation (EIP-5564) for Secp256k1 #1

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@

> **Warning**
>
> Still in early development. Don't use yet except you know what your doing!
> Still in early development.
> Don't use yet except you know what your doing!
>
> Expect breaking changes!

`crysol` is a set of Solidity libraries providing **clean** and **well-tested** implementations of cryptographic algorithms.
`crysol` is a collection of pure Solidity libraries providing clean
implementations of cryptographic algorithms for on- and offchain usage.

Differentiating to other projects, `crysol` also provides functionality to create cryptographic objects.

## Installation

Expand Down
19 changes: 13 additions & 6 deletions docs/Intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@

## What are `vmed` functions?

Traditionally, Solidity has been primarily used for verifying cryptographic objects and rarely for creating them, eg we verify ECDSA signatures in Solidity via `ecrecover` and create them via our non-Solidity based wallet libraries.
Traditionally, Solidity has been primarily used for verifying cryptographic
objects and rarely for creating them, eg we verify ECDSA signatures in Solidity
via `ecrecover` and create them via our non-Solidity based wallet libraries.

`crysol` takes a more comprehensive approach and also provides functionality to create cryptographic objects, allowing developers to test and experiment with cryptographic systems from within their Solidity environment.
`crysol` takes a more comprehensive approach and also provides functionality to
create cryptographic objects, allowing developers to test and research
cryptographic systems in a pure EVM environment.

However, most Solidity code is run on public blockchains - the last place one should perform operations requiring a private key as input.
However, most Solidity code is run on public blockchains - the last place one
should perform operations requiring a secret key as input.

To ensure operations using sensitive data are never run on non-local blockchains such functions are "`vmed`", meaning they revert whenever the blockchain's chain id is not `31337`.
To ensure operations using sensitive data are never run on non-local blockchains
such functions are "`vmed`", meaning they revert whenever the chain id is not `31337`.


## The Prelude

Many libraries include a code block called _prelude_ providing common internal functionality.
It provides the `vmed` modifier which protects certain functions from being called in non-local environments.
Many libraries include a code block called _prelude_ providing common internal
functionality such as the `vmed` modifier which protects certain functions from
being called in non-local environments.

The _prelude_ code is:

Expand Down
118 changes: 118 additions & 0 deletions examples/stealth-addresses/StealthSecp256k1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

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

import {Secp256k1, SecretKey, PublicKey} from "src/curves/Secp256k1.sol";

import {
StealthSecp256k1,
StealthAddress,
StealthMetaAddress,
SCHEME_ID
} from "src/stealth-addresses/StealthSecp256k1.sol";
import {
IERC5564Announcer,
ERC5564Announcer
} from "src/stealth-addresses/ERC5564Announcer.sol";

contract StealthSecp256k1Example is Script {
using StealthSecp256k1 for SecretKey;
using StealthSecp256k1 for StealthAddress;
using StealthSecp256k1 for StealthMetaAddress;

using Secp256k1 for SecretKey;
using Secp256k1 for PublicKey;

function run() public {
// Sender key pair.
SecretKey senderSk = Secp256k1.newSecretKey();
PublicKey memory senderPk = senderSk.toPublicKey();
logSender("Created key pair");

// Receiver key pairs consist of spending and viewing key pairs.
SecretKey receiverSpendSk = Secp256k1.newSecretKey();
PublicKey memory receiverSpendPk = receiverSpendSk.toPublicKey();
SecretKey receiverViewSk = Secp256k1.newSecretKey();
PublicKey memory receiverViewPk = receiverViewSk.toPublicKey();
logReceiver("Created key pair");

// There exists an ERC5564Announcer instance.
IERC5564Announcer announcer = IERC5564Announcer(new ERC5564Announcer());

// Receiver creates their stealth meta address.
// TODO: Note that these addresses need to be published somehow.
StealthMetaAddress memory receiverSma = StealthMetaAddress({
spendPk: receiverSpendPk,
viewPk: receiverViewPk
});
logReceiver(
string.concat(
"Created Ethereum stealth meta address: ", receiverSma.toString("eth")
)
);

// Sender creates stealth address from receiver's stealth meta address.
// TODO: receiver's stealh address must be argument for function, not
// an object to call a function on.
StealthAddress memory stealth = receiverSma.newStealthAddress();
logSender("Created stealth address from receiver's stealth meta address");

// Sender sends ETH to stealth.
vm.deal(senderPk.toAddress(), 1 ether);
vm.prank(senderPk.toAddress());
(bool ok, ) = stealth.recipient.call{value: 1 ether}("");
require(ok, "Sender: ETH transfer failed");
logSender("Send 1 ETH to stealth address");

// Sender announces tx via ERC5564Announcer.
vm.prank(senderPk.toAddress());
announcer.announce({
schemeId: SCHEME_ID,
stealthAddress: stealth.recipient,
ephemeralPubKey: stealth.ephPk.toBytes(),
// See ERC5564Announcer.sol for more info.
metadata: abi.encodePacked(
stealth.viewTag,
hex"eeeeeeee",
hex"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
uint(1 ether)
)
});
logSender("Announced tx via ERC-5564 announcer");

// Receiver checks announces stealth address.
require(
receiverViewSk.checkStealthAddress(receiverSpendPk, stealth),
"Check failed"
);
logReceiver("Verfied tx is based on own stealth meta address");

// Receiver computed stealth's private key.
console.log("Receiver: Computes private key for stealth address");
SecretKey stealthSk = receiverSpendSk
.computeStealthSecretKey(receiverViewSk, stealth);

// Verify computed private key's address matches stealth's recipient
// address.
console.log("Receiver: Verifies access on stealth address");
require(
stealthSk.toPublicKey().toAddress() == stealth.recipient,
"Private key computation failed"
);
}

function logSender(string memory message) internal {
console.log(
string.concat(StdStyle.yellow("[SENDER] "), message)
);
}

function logReceiver(string memory message) internal {
console.log(
string.concat(StdStyle.blue("[RECEIVER] "), message)
);
}
}
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib', 'spec', 'examples', 'unsafe']
libs = ['lib', 'examples', 'unsafe']
ffi = true

# Compilation
evm_version = "shanghai"
solc_version = "0.8.23"
via_ir = false
via_ir = true
optimizer = false
extra_output_files = ["irOptimized"]

Expand Down
4 changes: 2 additions & 2 deletions src/curves/Secp256k1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
import {Random} from "../Random.sol";

/**
* @notice Secret key is an secp256k1 secret key
* @notice SecretKey is an secp256k1 secret key
*
* @dev Note that a secret key MUST be a field element, ie sk ∊ [1, Q).
*
Expand Down Expand Up @@ -177,7 +177,7 @@ library Secp256k1 {
/// - Scalar not in [1, Q)
function secretKeyFromUint(uint scalar) internal pure returns (SecretKey) {
if (scalar == 0 || scalar >= Q) {
revert("InvalidScalar()");
revert("ScalarInvalid()");
}

return SecretKey.wrap(scalar);
Expand Down
Loading
Loading