Skip to content

Commit

Permalink
Adds announcer stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
pmerkleplant committed Dec 2, 2023
1 parent 8b86dc3 commit b6b842a
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 2 deletions.
105 changes: 105 additions & 0 deletions examples/stealth-addresses/StealthSecp256k1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

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

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

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

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

using Secp256k1 for PrivateKey;
using Secp256k1 for PublicKey;

function run() public {
// Sender key pair.
console.log("Sender: Creates key pair");
PrivateKey senderPrivKey = Secp256k1.newPrivateKey();
PublicKey memory senderPubKey = senderPrivKey.toPublicKey();

// Receiver key pairs consist of spending and viewing key pairs.
console.log("Receiver: Creates key pairs");
PrivateKey receiverSpendingPrivKey = Secp256k1.newPrivateKey();
PublicKey memory receiverSpendingPubKey =
receiverSpendingPrivKey.toPublicKey();
PrivateKey receiverViewPrivKey = Secp256k1.newPrivateKey();
PublicKey memory receiverViewPubKey = receiverViewPrivKey.toPublicKey();

// 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.
console.log("Receiver: Creates stealth meta address");
StealthMetaAddress memory receiverStealthMeta;
receiverStealthMeta = StealthMetaAddress({
spendingPubKey: receiverSpendingPubKey,
viewingPubKey: receiverViewPubKey
});

// Sender creates stealth address from receiver's stealth meta address.
console.log(
"Sender: Creates stealth address based on receiver's meta address"
);
StealthAddress memory stealth = receiverStealthMeta.newStealthAddress();

// Sender sends ETH to stealth.
console.log("Sender: Sends ETH to receiver's stealth address");
vm.deal(senderPubKey.toAddress(), 1 ether);
vm.prank(senderPubKey.toAddress());
(bool ok, ) = stealth.recipient.call{value: 1 ether}("");
require(ok, "Sender: ETH transfer failed");

// Sender announces tx via ERC5564Announcer.
console.log("Sender: Announces tx via ERC5564Announcer");
vm.prank(senderPubKey.toAddress());
announcer.announce({
schemeId: SCHEME_ID,
stealthAddress: stealth.recipient,
ephemeralPubKey: stealth.ephemeralPubKey.toBytes(),
// See ERC5564Announcer.sol for more info.
metadata: abi.encodePacked(
stealth.viewTag,
hex"eeeeeeee",
hex"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
uint(1 ether)
)
});

// Receiver checks announces stealth address.
console.log("Receiver: Verifies tx is based on own meta address");
require(
receiverViewPrivKey.checkStealthAddress(
receiverSpendingPubKey, stealth
),
"Check failed"
);

// Receiver computed stealth's private key.
console.log("Receiver: Computes private key for stealth address");
PrivateKey stealthPrivKey = receiverSpendingPrivKey
.computeStealthPrivateKey(receiverViewPrivKey, stealth);

// Verify computed private key's address matches stealth's recipient
// address.
console.log("Receiver: Verifies access on stealth address");
require(
stealthPrivKey.toPublicKey().toAddress() == stealth.recipient,
"Private key computation failed"
);
}
}
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib', 'spec', 'examples', 'unsafe']
libs = ['lib', 'examples', 'unsafe']
ffi = true

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

/**
* @title IERC5561Announcer
*
* @notice Interface to announce a tx to an [EIP-5564] stealth address
*
* @dev Metadata Specification and Recommendations
*
* The first byte of the metadata MUST be the view tag. The view tag provides
* a probabilistic filter to skip computations when checking announcements.
*
* The following recommendations are given in [EIP-5564]:
*
* - Tx transferring the native token, eg ETH:
*
* Index | Description | Length in bytes
* -----------------------------------------------------------------------------
* [0x00] | View tag | 1
* [0x01:0x04] | `0xeeeeeeee` | 4
* [0x05:0x24] | `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` | 20
* [0x18:0x38] | Amount in wei | 32
*
* - Tx involving a contract call with a single argument, eg ERC-20/ERC-721
* transfers:
*
* Index | Description | Length in bytes
* -----------------------------------------------------------------------------
* [0x00] | View tag | 1
* [0x01:0x04] | Solidity function selector | 4
* [0x05:0x24] | Contract address | 20
* [0x18:0x38] | One word argument, eg token amount | 32
*
* @custom:references
* - [EIP-5564]: https://eips.ethereum.org/EIPS/eip-5564
* - [EIP-5564 Scheme Registry]: https://eips.ethereum.org/assets/eip-5564/scheme_ids
*/
interface IERC5564Announcer {
/// @notice Emitted to announce a tx to a stealth address.
///
/// @param schemeId Scheme id based on [EIP-5564 Scheme Registry] registry.
/// @param stealthAddress The stealth address.
/// @param caller The address who announced the tx.
/// @param ephemeralPubKey The ephemeral public key created during the
/// stealth address generation.
/// @param metadata Bytes blob providing the view tag and arbitrary
/// additional metadata. Note that [EIP-5564] provides
/// recommendations.
event Announcement(
uint indexed schemeId,
address indexed stealthAddress,
address indexed caller,
bytes ephemeralPubKey,
bytes metadata
);

/// @notice Announces a tx to stealth address `stealthAddress` using scheme
/// `schemeId` and ephemeral public key `ephemeralPubKey`. View tag
/// and additional metadata are provided via `metadata`.
///
/// @param schemeId Scheme id based on [EIP-5564 Scheme Registry] registry.
/// @param stealthAddress The stealth address.
/// @param ephemeralPubKey The ephemeral public key created during the
/// stealth address generation.
/// @param metadata Bytes blob providing the view tag and arbitrary
/// additional metadata. Note that [EIP-5564] provides
/// recommendations.
function announce(
uint schemeId,
address stealthAddress,
bytes memory ephemeralPubKey,
bytes memory metadata
) external;
}

/**
* @title ERC5564Announcer
*
* @notice Minimal [EIP-5564] stealth address announcement contract
*/
contract ERC5564Announcer is IERC5564Announcer {
/// @inheritdoc IERC5564Announcer
function announce(
uint schemeId,
address stealthAddress,
bytes memory ephemeralPubKey,
bytes memory metadata
) external {
emit Announcement(
schemeId, stealthAddress, msg.sender, ephemeralPubKey, metadata
);
}
}
14 changes: 14 additions & 0 deletions src/stealth-addresses/StealthSecp256k1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ struct StealthAddress {
uint8 viewTag;
}

// TODO: Differentiate between EIPs and ERCs.

/**
* @title StealthSecp256k1
*
* @notice Stealth Addresses for secp256k1 following [EIP-5564]
*
* @dev
*
* @custom:references
* - [EIP-5564]: https://eips.ethereum.org/EIPS/eip-5564
*
* @author crysol (https://github.com/pmerkleplant/crysol)
*/
library StealthSecp256k1 {
using Secp256k1 for PrivateKey;
using Secp256k1 for PublicKey;
Expand Down
6 changes: 5 additions & 1 deletion test/curves/secp256k1/Secp256k1Arithmetic.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,11 @@ contract Secp256k1ArithmeticWrapper {
return point.yParity();
}

function equals(Point memory point, Point memory other) public pure returns (bool) {
function equals(Point memory point, Point memory other)
public
pure
returns (bool)
{
return point.equals(other);
}

Expand Down

0 comments on commit b6b842a

Please sign in to comment.