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

Add recovery functions to EthereumSignedTransaction #173

Merged
merged 6 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
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 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

Check warning on line 216 in Sources/Core/Transaction/EthereumTransaction.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Core/Transaction/EthereumTransaction.swift#L216

Added line #L216 was not covered by tests
}
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

Check warning on line 234 in Sources/Core/Transaction/EthereumTransaction.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Core/Transaction/EthereumTransaction.swift#L234

Added line #L234 was not covered by tests
}
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

Check warning on line 256 in Sources/Core/Transaction/EthereumTransaction.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Core/Transaction/EthereumTransaction.swift#L256

Added line #L256 was not covered by tests
}
}
}

public struct EthereumSignedTransaction {

// MARK: - Properties
Expand Down Expand Up @@ -365,54 +390,30 @@
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

Check warning on line 399 in Sources/Core/Transaction/EthereumTransaction.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Core/Transaction/EthereumTransaction.swift#L398-L399

Added lines #L398 - L399 were not covered by tests
}

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

Check warning on line 413 in Sources/Core/Transaction/EthereumTransaction.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Core/Transaction/EthereumTransaction.swift#L413

Added line #L413 was not covered by tests
} 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 @@
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 @@
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

Check warning on line 730 in Sources/Core/Transaction/EthereumTransaction.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Core/Transaction/EthereumTransaction.swift#L730

Added line #L730 was not covered by tests
} 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