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
99 changes: 99 additions & 0 deletions contracts/contracts/EIP155Signer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {Sapphire} from "./Sapphire.sol";
import {EthereumUtils} 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;
}

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

/**
* 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
view
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 - 27) + (block.chainid * 2) + 35);
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 a = new bytes[](9);
a[0] = RLPWriter.writeUint(rawTx.nonce);
a[1] = RLPWriter.writeUint(rawTx.gasPrice);
a[2] = RLPWriter.writeUint(rawTx.gasLimit);
a[3] = RLPWriter.writeAddress(rawTx.to);
a[4] = RLPWriter.writeUint(rawTx.value);
a[5] = RLPWriter.writeBytes(rawTx.data);
a[6] = RLPWriter.writeUint(rawTx.chainId);
a[7] = RLPWriter.writeUint(0);
a[8] = RLPWriter.writeUint(0);
CedarMist marked this conversation as resolved.
Show resolved Hide resolved

bytes32 digest = keccak256(RLPWriter.writeList(a));

(ret.r, ret.s, ret.v) = 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) {
return
encodeSignedTx(
transaction,
signRawTx(transaction, publicAddress, secretKey)
);
}
}
94 changes: 76 additions & 18 deletions contracts/contracts/EthereumUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

pragma solidity ^0.8.0;

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

library EthereumUtils {
uint256 internal constant K256_P =
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f;
Expand Down Expand Up @@ -182,17 +184,34 @@ library EthereumUtils {
}
}

error toEthereumSignature_Error();
error recoverV_Error();

function recoverV(
address pubkeyAddr,
bytes32 digest,
bytes32 sigR,
bytes32 sigS
) internal pure returns (uint8 sigV) {
sigV = 27;

if (ecrecover(digest, sigV, sigR, sigS) != pubkeyAddr) {
sigV = 28;

if (ecrecover(digest, sigV, sigR, sigS) != 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 sigR
* @return sigS
* @return sigV sign bit / recovery id
* @custom:see https://gavwood.com/paper.pdf (206)
*/
function toEthereumSignature(
Expand All @@ -203,24 +222,63 @@ library EthereumUtils {
internal
view
returns (
address pubkey_addr,
bytes32 r,
bytes32 s,
uint8 v
address pubkeyAddr,
bytes32 sigR,
bytes32 sigS,
uint8 sigV
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
)
{
pubkeyAddr = k256PubkeyToEthereumAddress(pubkey);

(sigR, sigS) = splitDERSignature(signature);

sigV = recoverV(pubkeyAddr, digest, sigR, sigS);
}

function sign(
address pubkeyAddr,
bytes32 secretKey,
bytes32 digest
)
internal
view
returns (
bytes32 sigR,
bytes32 sigS,
uint8 sigV
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
)
{
pubkey_addr = k256PubkeyToEthereumAddress(pubkey);
bytes memory sig = Sapphire.sign(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
abi.encodePacked(secretKey),
abi.encodePacked(digest),
""
);

(r, s) = splitDERSignature(signature);
(sigR, sigS) = splitDERSignature(sig);

v = 27;
sigV = recoverV(pubkeyAddr, digest, sigR, sigS);
}

if (ecrecover(digest, v, r, s) != pubkey_addr) {
v = 28;
/**
* 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 pubkeyAddr, bytes32 secretKey)
{
bytes memory randSeed = Sapphire.randomBytes(32, "");

if (ecrecover(digest, v, r, s) != pubkey_addr) {
revert toEthereumSignature_Error();
}
}
secretKey = bytes32(randSeed);

(bytes memory pk, bytes memory tmp) = Sapphire.generateSigningKeyPair(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
randSeed
);

pubkeyAddr = k256PubkeyToEthereumAddress(pk);
}
}