Skip to content

Commit

Permalink
feat: Adds optimized secret key to address function
Browse files Browse the repository at this point in the history
  • Loading branch information
StackOverflowExcept1on authored Jun 26, 2024
1 parent a764a25 commit ac04a6b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 6 deletions.
6 changes: 4 additions & 2 deletions examples/secp256k1/Secp256k1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ contract Secp256k1Example is Script {
*/

// Derive common constructs.
address addr = pk.toAddress();
address addr1 = sk.toAddress();
address addr2 = pk.toAddress();
assert(addr1 == addr2);
/*
bytes32 digest = pk.toHash();
uint yParity = pk.yParity();
*/
console.log("Derived address:");
console.log(addr);
console.log(addr1);
console.log("");

// Serialization.
Expand Down
15 changes: 15 additions & 0 deletions src/onchain/secp256k1/Secp256k1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ library Secp256k1 {
return SecretKey.unwrap(sk);
}

/// @dev Returns the address of secret key `sk`.
///
/// @dev Note that this function is substantially cheaper than computing
/// `sk`'s public key and deriving it's address manually.
///
/// @dev Reverts if:
/// Secret key invalid
function toAddress(SecretKey sk) internal pure returns (address) {
if (!sk.isValid()) {
revert("SecretKeyInvalid()");
}

return Secp256k1Arithmetic.G().mulToAddress(sk.asUint());
}

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

Expand Down
45 changes: 43 additions & 2 deletions src/onchain/secp256k1/Secp256k1Arithmetic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct ProjectivePoint {
* - [Yellow Paper]: https://github.com/ethereum/yellowpaper
* - [Renes-Costello-Batina 2015]: https://eprint.iacr.org/2015/1060.pdf
* - [Dubois 2023]: https://eprint.iacr.org/2023/939.pdf
* - [Vitalik 2018]: https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384
*
* @author verklegarden
* @custom:repository github.com/verklegarden/crysol
Expand All @@ -72,6 +73,10 @@ library Secp256k1Arithmetic {
/// computed via x^{SQUARE_ROOT_EXPONENT} (mod p).
uint private constant SQUARE_ROOT_EXPONENT = (P + 1) / 4;

/// @dev Used as substitute for `Identity().intoPublicKey().toAddress()`.
address private constant IDENTITY_ADDRESS =
0x2dCC482901728b6df477f4fB2F192733A005d396;

//--------------------------------------------------------------------------
// Secp256k1 Constants
//
Expand Down Expand Up @@ -319,7 +324,43 @@ library Secp256k1Arithmetic {
return result;
}

// TODO: Provide verifyMul(point,scalar,want) using ecrecover?
/// @dev Returns the product of point `point` and scalar `scalar` as
/// address.
///
/// @dev Note that this function is substantially cheaper than
/// `mul(ProjectivePoint,uint)(ProjectivePoint)` with the caveat that
/// only the point's address is returned instead of the point itself.
function mulToAddress(Point memory point, uint scalar)
internal
pure
returns (address)
{
if (scalar >= Q) {
revert("ScalarMustBeFelt()");
}

if (scalar == 0 || point.isIdentity()) {
return IDENTITY_ADDRESS;
}

// Note that ecrecover can be abused to perform an elliptic curve
// multiplication with the caveat that the point's address is returned
// instead of the point itself.
//
// For further details, see [Vitalik 2018] and [SEC-1 v2] section 4.1.6
// "Public Key Recovery Operation".

uint8 v;
// Unchecked because point.yParity() ∊ {0, 1} which cannot overflow by
// adding 27.
unchecked {
v = uint8(point.yParity() + 27);
}
uint r = point.x;
uint s = mulmod(r, scalar, Q);

return ecrecover(0, v, bytes32(r), bytes32(s));
}

//--------------------------------------------------------------------------
// Type Conversions
Expand Down Expand Up @@ -375,7 +416,7 @@ library Secp256k1Arithmetic {

// Return as Point(point.x, point.y).
// Note that from this moment, point.z is dirty memory!
// TODO: Zero dirty memory.
// TODO: Clean dirty memory.
assembly ("memory-safe") {
p := point
}
Expand Down
31 changes: 30 additions & 1 deletion test/onchain/secp256k1/Secp256k1.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,35 @@ contract Secp256k1Test is Test {

// -- asUint

function testFuzz_SecertKey_asUint(uint seed) public {
function testFuzz_SecretKey_asUint(uint seed) public {
assertEq(seed, wrapper.asUint(SecretKey.wrap(seed)));
}

function testFuzz_SecretKey_toAddress(SecretKey sk) public {
vm.assume(sk.isValid());

address got = wrapper.toAddress(sk);
address want = vm.addr(sk.asUint());

assertEq(got, want);
}

function test_SecretKey_toAddress_RevertsIf_SecretKeyInvalid_SecretKeyZero()
public
{
vm.expectRevert("SecretKeyInvalid()");
wrapper.toAddress(SecretKey.wrap(0));
}

function testFuzz_SecretKey_toAddress_RevertsIf_SecretKeyInvalid_SecretKeyGreaterOrEqualToQ(
uint seed
) public {
uint scalar = _bound(seed, Secp256k1.Q, type(uint).max);

vm.expectRevert("SecretKeyInvalid()");
wrapper.toAddress(SecretKey.wrap(scalar));
}

//--------------------------------------------------------------------------
// Test: Public Key

Expand Down Expand Up @@ -318,6 +343,10 @@ contract Secp256k1Wrapper {
return sk.asUint();
}

function toAddress(SecretKey sk) public pure returns (address) {
return sk.toAddress();
}

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

Expand Down
51 changes: 50 additions & 1 deletion test/onchain/secp256k1/Secp256k1Arithmetic.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ contract Secp256k1ArithmeticTest is Test {
using Secp256k1Offchain for SecretKey;
using Secp256k1 for SecretKey;
using Secp256k1 for PublicKey;
using Secp256k1 for Point;
using Secp256k1Arithmetic for Point;
using Secp256k1Arithmetic for ProjectivePoint;

Expand Down Expand Up @@ -235,7 +236,6 @@ contract Secp256k1ArithmeticTest is Test {
vm.assume(sk.isValid());

ProjectivePoint memory id = Secp256k1Arithmetic.ProjectiveIdentity();

assertTrue(wrapper.mul(id, sk.asUint()).isIdentity());
}

Expand All @@ -249,6 +249,47 @@ contract Secp256k1ArithmeticTest is Test {
wrapper.mul(point, scalar);
}

// -- mulToAddress

function testFuzz_ProjectivePoint_mulToAddress(
SecretKey sk,
uint scalarSeed
) public {
vm.assume(sk.isValid());

Point memory point = sk.toPublicKey().intoPoint();
uint scalar = _bound(scalarSeed, 1, Secp256k1Arithmetic.Q - 1);

address got = wrapper.mulToAddress(point, scalar);
// forgefmt: disable-next-item
address want = point.toProjectivePoint()
.mul(scalar)
.intoPoint()
.intoPublicKey()
.toAddress();

assertEq(got, want);
}

function testFuzz_ProjectivePoint_mulToAddress_ReturnsIdentityIfScalarIsZero(
Point memory point
) public {
assertEq(
wrapper.mulToAddress(point, 0),
Secp256k1Arithmetic.Identity().intoPublicKey().toAddress()
);
}

function testFuzz_ProjectivePoint_mulToAddress_RevertsIf_ScalarNotFelt(
Point memory point,
uint scalar
) public {
vm.assume(scalar >= Secp256k1Arithmetic.Q);

vm.expectRevert("ScalarMustBeFelt()");
wrapper.mulToAddress(point, scalar);
}

//--------------------------------------------------------------------------
// Type Conversions

Expand Down Expand Up @@ -587,6 +628,14 @@ contract Secp256k1ArithmeticWrapper {
return point.mul(scalar);
}

function mulToAddress(Point memory point, uint scalar)
public
pure
returns (address)
{
return point.mulToAddress(scalar);
}

//--------------------------------------------------------------------------
// Type Conversions

Expand Down

0 comments on commit ac04a6b

Please sign in to comment.