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

contracts: Added EIP-155 signer and extended EthereumUtils #154

Merged
merged 10 commits into from
Jul 17, 2023
85 changes: 85 additions & 0 deletions contracts/contracts/EIP155Signer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {Sapphire} from "./Sapphire.sol";
import {EthereumUtils, SignatureRSV} from "./EthereumUtils.sol";
import {RLPWriter} from "./RLPWriter.sol";

/**
* @title Ethereum EIP-155 compatible transaction signer & encoder
*/
library EIP155Signer {
struct EthTx {
uint64 nonce;
uint256 gasPrice;
uint64 gasLimit;
address to;
uint256 value;
bytes data;
uint256 chainId;
}

/**
* Encode a signed EIP-155 transaction
* @param rawTx Transaction which was signed
* @param rsv R, S & V parameters of signature
*/
function encodeSignedTx(EthTx memory rawTx, SignatureRSV memory rsv)
internal
pure
returns (bytes memory)
{
bytes[] memory b = new bytes[](9);
b[0] = RLPWriter.writeUint(rawTx.nonce);
b[1] = RLPWriter.writeUint(rawTx.gasPrice);
b[2] = RLPWriter.writeUint(rawTx.gasLimit);
b[3] = RLPWriter.writeAddress(rawTx.to);
b[4] = RLPWriter.writeUint(rawTx.value);
b[5] = RLPWriter.writeBytes(rawTx.data);
b[6] = RLPWriter.writeUint(rsv.v);
b[7] = RLPWriter.writeUint(uint256(rsv.r));
b[8] = RLPWriter.writeUint(uint256(rsv.s));
return RLPWriter.writeList(b);
}

/**
* Sign a raw transaction, which will then need to be encoded to include the signature
* @param rawTx Transaction to sign
* @param pubkeyAddr Ethereum address of secret key
* @param secretKey Secret key used to sign
*/
function signRawTx(
EthTx memory rawTx,
address pubkeyAddr,
bytes32 secretKey
) internal view returns (SignatureRSV memory ret) {
bytes memory encoded = encodeSignedTx(
rawTx,
SignatureRSV({v: rawTx.chainId, r: 0, s: 0})
);

bytes32 digest = keccak256(abi.encodePacked(encoded));

ret = EthereumUtils.sign(pubkeyAddr, secretKey, digest);
}

/**
* Sign a transaction, returning it in EIP-155 encoded form
* @param publicAddress Ethereum address of secret key
* @param secretKey Secret key used to sign
* @param transaction Transaction to sign
*/
function sign(
address publicAddress,
bytes32 secretKey,
EthTx memory transaction
) internal view returns (bytes memory) {
SignatureRSV memory rsv = signRawTx(
transaction,
publicAddress,
secretKey
);
rsv.v = (rsv.v - 27) + (transaction.chainId * 2) + 35;
return encodeSignedTx(transaction, rsv);
}
}
99 changes: 74 additions & 25 deletions contracts/contracts/EthereumUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

pragma solidity ^0.8.0;

import {Sapphire} from "./Sapphire.sol";

struct SignatureRSV {
bytes32 r;
bytes32 s;
uint256 v;
}

library EthereumUtils {
uint256 internal constant K256_P =
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f;
Expand Down Expand Up @@ -124,14 +132,13 @@ library EthereumUtils {
* This function only works if either `r` or `s` are 256bits or lower.
*
* @param der DER encoded ECDSA signature
* @return r ECDSA R point X coordinate
* @return s ECDSA s scalar
* @return rsv ECDSA R point X coordinate, and S scalar
* @custom:see https://bitcoin.stackexchange.com/questions/58853/how-do-you-figure-out-the-r-and-s-out-of-a-signature-using-python
*/
function splitDERSignature(bytes memory der)
internal
pure
returns (bytes32 r, bytes32 s)
returns (SignatureRSV memory rsv)
{
if (der.length < 8) revert DER_Split_Error();
if (der[0] != 0x30) revert DER_Split_Error();
Expand Down Expand Up @@ -164,6 +171,9 @@ library EthereumUtils {
sLen -= 1;
}

bytes32 r;
bytes32 s;

assembly {
r := mload(add(der, add(32, rOffset)))
s := mload(add(der, add(32, sOffset)))
Expand All @@ -180,47 +190,86 @@ library EthereumUtils {
if (sLen < 32) {
s >>= 8 * (32 - sLen);
}

rsv.r = r;
rsv.s = s;
}

error toEthereumSignature_Error();
error recoverV_Error();

function recoverV(
address pubkeyAddr,
bytes32 digest,
SignatureRSV memory rsv
) internal pure {
rsv.v = 27;

if (ecrecover(digest, uint8(rsv.v), rsv.r, rsv.s) != pubkeyAddr) {
rsv.v = 28;

if (ecrecover(digest, uint8(rsv.v), rsv.r, rsv.s) != pubkeyAddr) {
revert recoverV_Error();
}
}
}

/**
* Convert a Secp256k1PrehashedKeccak256 signature to one accepted by ecrecover
* @param pubkey 33 byte compressed public key
* @param digest 32 byte pre-hashed message digest
* @param signature ASN.1 DER encoded signature, as returned from `Sapphire.sign`
* @return pubkey_addr 20 byte Ethereum address
* @return r
* @return s
* @return v sign bit / recovery id
* @return pubkeyAddr 20 byte Ethereum address
* @return rsv Ethereum EcDSA RSV signature values
* @custom:see https://gavwood.com/paper.pdf (206)
*/
function toEthereumSignature(
bytes memory pubkey,
bytes32 digest,
bytes memory signature
)
) internal view returns (address pubkeyAddr, SignatureRSV memory rsv) {
pubkeyAddr = k256PubkeyToEthereumAddress(pubkey);

rsv = splitDERSignature(signature);

recoverV(pubkeyAddr, digest, rsv);
}

function sign(
address pubkeyAddr,
bytes32 secretKey,
bytes32 digest
) internal view returns (SignatureRSV memory rsv) {
bytes memory signature = Sapphire.sign(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
abi.encodePacked(secretKey),
abi.encodePacked(digest),
""
);

rsv = splitDERSignature(signature);

recoverV(pubkeyAddr, digest, rsv);
}

/**
* Generates an Ethereum compatible SEC P256 k1 keypair and corresponding public address
* @return pubkeyAddr Ethereum address
* @return secretKey Secret key used for signing
*/
function generateKeypair()
internal
view
returns (
address pubkey_addr,
bytes32 r,
bytes32 s,
uint8 v
)
returns (address pubkeyAddr, bytes32 secretKey)
{
pubkey_addr = k256PubkeyToEthereumAddress(pubkey);

(r, s) = splitDERSignature(signature);
bytes memory randSeed = Sapphire.randomBytes(32, "");

v = 27;
secretKey = bytes32(randSeed);

if (ecrecover(digest, v, r, s) != pubkey_addr) {
v = 28;
(bytes memory pk, bytes memory tmp) = Sapphire.generateSigningKeyPair(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
randSeed
);

if (ecrecover(digest, v, r, s) != pubkey_addr) {
revert toEthereumSignature_Error();
}
}
pubkeyAddr = k256PubkeyToEthereumAddress(pk);
}
}