Skip to content

Commit

Permalink
Add recovery functions to EthereumSignedTransaction (#173)
Browse files Browse the repository at this point in the history
* Add from, publicKey and unsignedMessage recovery functions to EthereumSignedMessage

* Simplify unsignedMessage function

* Add recoverUnsignedTx to EthereumSignedTransaction

* Remove some redundancies

* Update build-and-test.yml

* feat: codecov token in github ci

---------

Co-authored-by: Koray Koska <[email protected]>
  • Loading branch information
Florian-S-A-W and koraykoska authored May 2, 2024
1 parent 9c362d2 commit b85187d
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 98 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ jobs:
if: ${{ matrix.os == 'macos-latest' }}
run: xcrun llvm-cov export -format="lcov" .build/debug/Web3PackageTests.xctest/Contents/MacOS/Web3PackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov
- name: Upload Test Coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
if: ${{ matrix.os == 'macos-latest' }}
with:
files: ./info.lcov
fail_ci_if_error: true
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
179 changes: 109 additions & 70 deletions Sources/Core/Transaction/EthereumTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,8 @@ public struct EthereumTransaction: Codable {
guard let nonce = nonce, let gasPrice = gasPrice, let gasLimit = gasLimit, let value = value else {
throw EthereumSignedTransaction.Error.transactionInvalid
}
let rlp = RLPItem(
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: to,
value: value,
data: data,
v: chainId,
r: 0,
s: 0
)
let rawRlp = try RLPEncoder().encode(rlp)
let signature = try privateKey.sign(message: rawRlp)
let messageToSign = try self.messageToSign(chainId: chainId)
let signature = try privateKey.sign(message: messageToSign)

let v: BigUInt
if chainId.quantity == 0 {
Expand Down Expand Up @@ -190,24 +179,8 @@ public struct EthereumTransaction: Codable {
if chainId.quantity == BigUInt(0) {
throw EthereumSignedTransaction.Error.chainIdNotSet(msg: "EIP1559 transactions need a chainId")
}

let rlp = RLPItem(
nonce: nonce,
gasPrice: gasPrice ?? EthereumQuantity(integerLiteral: 0),
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
gasLimit: gasLimit,
to: to,
value: value,
data: data,
chainId: chainId,
accessList: accessList,
transactionType: transactionType
)
let rawRlp = try RLPEncoder().encode(rlp)
var messageToSign = Bytes()
messageToSign.append(0x02)
messageToSign.append(contentsOf: rawRlp)

var messageToSign = try self.messageToSign(chainId: chainId)
let signature = try privateKey.sign(message: messageToSign)

let v = BigUInt(signature.v)
Expand All @@ -233,6 +206,58 @@ public struct EthereumTransaction: Codable {
}
}

public extension EthereumTransaction {

fileprivate func messageToSign(chainId: EthereumQuantity) throws -> Bytes {
let rlpEncoder = RLPEncoder()

if self.transactionType == .legacy {
guard let nonce = nonce, let gasPrice = gasPrice, let gasLimit = gasLimit, let value = value else {
throw EthereumSignedTransaction.Error.transactionInvalid
}
let rlp = RLPItem(
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: to,
value: value,
data: data,
v: chainId,
r: 0,
s: 0
)
let rawRlp = try RLPEncoder().encode(rlp)
return rawRlp
} else if self.transactionType == .eip1559 {
guard let nonce = nonce, let maxFeePerGas = maxFeePerGas, let maxPriorityFeePerGas = maxPriorityFeePerGas,
let gasLimit = gasLimit, let value = value else {
throw EthereumSignedTransaction.Error.transactionInvalid
}
let rlp = RLPItem(
nonce: nonce,
gasPrice: gasPrice ?? EthereumQuantity(integerLiteral: 0),
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
gasLimit: gasLimit,
to: to,
value: value,
data: data,
chainId: chainId,
accessList: accessList,
transactionType: transactionType
)
let rawRlp = try rlpEncoder.encode(rlp)
var messageToSign = Bytes()
messageToSign.append(0x02)
messageToSign.append(contentsOf: rawRlp)

return messageToSign
} else {
throw EthereumSignedTransaction.Error.transactionInvalid
}
}
}

public struct EthereumSignedTransaction {

// MARK: - Properties
Expand Down Expand Up @@ -365,54 +390,30 @@ public struct EthereumSignedTransaction {
recId = v.quantity
}
}
let rlp = RLPItem(
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: to,
value: value,
data: data,
v: chainId,
r: 0,
s: 0
)
if let _ = try? EthereumPublicKey(message: RLPEncoder().encode(rlp), v: EthereumQuantity(quantity: recId), r: r, s: s) {
return true
do {
let messageToSign = try self.unsignedTransaction().messageToSign(chainId: self.chainId)
if let _ = try? EthereumPublicKey(message: messageToSign, v: EthereumQuantity(quantity: recId), r: r, s: s) {
return true
}
} catch {
return false
}

return false
}

private func verifyEip1559Signature() -> Bool {
let rlp = RLPItem(
nonce: nonce,
gasPrice: gasPrice,
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
gasLimit: gasLimit,
to: to,
value: value,
data: data,
v: 0,
r: 0,
s: 0,
chainId: chainId,
accessList: accessList,
transactionType: transactionType
)
var messageToSign = Bytes()
messageToSign.append(0x02)
do {
try messageToSign.append(contentsOf: RLPEncoder().encode(rlp))
let messageToSign = try self.unsignedTransaction().messageToSign(chainId: self.chainId)

if let _ = try? EthereumPublicKey(message: messageToSign, v: v, r: r, s: s) {
return true
}

return false
} catch {
return false
}

if let _ = try? EthereumPublicKey(message: messageToSign, v: v, r: r, s: s) {
return true
}

return false
}

// MARK: - Errors
Expand All @@ -437,7 +438,7 @@ extension EthereumSignedTransaction {
rawTxBytes.removeFirst()
}
do {
var rlp = try RLPDecoder().decode(rawTxBytes)
let rlp = try RLPDecoder().decode(rawTxBytes)

try self.init(rlp: rlp)
} catch {
Expand Down Expand Up @@ -712,3 +713,41 @@ extension EthereumSignedTransaction: Hashable {
hasher.combine(transactionType)
}
}

extension EthereumSignedTransaction {

public func from() throws -> EthereumAddress {
return try publicKey().address
}

public func publicKey() throws -> EthereumPublicKey {
let messageToSign = try self.unsignedTransaction().messageToSign(chainId: self.chainId)
var recId: BigUInt
if v.quantity >= BigUInt(35) + (BigUInt(2) * chainId.quantity) {
recId = v.quantity - BigUInt(35) - (BigUInt(2) * chainId.quantity)
} else {
if v.quantity >= 27 {
recId = v.quantity - 27
} else {
recId = v.quantity
}
}
return try EthereumPublicKey(message: messageToSign, v: EthereumQuantity(quantity: recId), r: self.r, s: self.s)
}

public func unsignedTransaction() throws -> EthereumTransaction {
return EthereumTransaction(
nonce: self.nonce,
gasPrice: self.gasPrice,
maxFeePerGas: self.maxFeePerGas,
maxPriorityFeePerGas: self.maxPriorityFeePerGas,
gasLimit: self.gasLimit,
to: self.to,
value: self.value,
data: self.data,
accessList: self.accessList,
transactionType: self.transactionType
)
}

}
Loading

0 comments on commit b85187d

Please sign in to comment.