diff --git a/Sources/Core/Providers/Web3Provider.swift b/Sources/Core/Providers/Web3Provider.swift index f6b5de43..bbbd2e72 100644 --- a/Sources/Core/Providers/Web3Provider.swift +++ b/Sources/Core/Providers/Web3Provider.swift @@ -39,8 +39,8 @@ public struct Web3Response { case subscriptionCancelled(Swift.Error?) } - public enum Status { - case success(Result) + public enum Status { + case success(StatusResult) case failure(Swift.Error) } @@ -108,7 +108,7 @@ extension Web3Response.Status { return !isSuccess } - public var result: Result? { + public var result: StatusResult? { switch self { case .success(let value): return value diff --git a/Sources/Core/Transaction/EthereumPublicKey.swift b/Sources/Core/Transaction/EthereumPublicKey.swift index 933cd84e..c0813f1c 100644 --- a/Sources/Core/Transaction/EthereumPublicKey.swift +++ b/Sources/Core/Transaction/EthereumPublicKey.swift @@ -118,6 +118,9 @@ public final class EthereumPublicKey { * EthereumPublicKey.Error.internalError if a secp256k1 library call or another internal call fails. */ public init(message: Bytes, v: EthereumQuantity, r: EthereumQuantity, s: EthereumQuantity, ctx: OpaquePointer? = nil) throws { + let originalR = r + let originalS = s + // Create context let finalCtx: OpaquePointer if let ctx = ctx { @@ -171,7 +174,7 @@ public final class EthereumPublicKey { defer { free(pubkey) } - var hash = SHA3(variant: .keccak256).calculate(for: rawSig) + var hash = SHA3(variant: .keccak256).calculate(for: message) guard hash.count == 32 else { throw Error.internalError } @@ -196,6 +199,13 @@ public final class EthereumPublicKey { } pubHash = Array(pubHash[12...]) self.address = try EthereumAddress(rawAddress: pubHash) + + // Final check for signature validity + + let signatureVerified = try verifySignature(message: message, v: vUInt, r: originalR.quantity, s: originalS.quantity) + if !signatureVerified { + throw Error.signatureMalformed + } } /** @@ -220,7 +230,6 @@ public final class EthereumPublicKey { // MARK: - Convenient functions - /* public func verifySignature(message: Bytes, v: UInt, r: BigUInt, s: BigUInt) throws -> Bool { // Get public key var rawpubKey = rawPublicKey @@ -246,12 +255,12 @@ public final class EthereumPublicKey { guard v <= Int32.max else { throw Error.signatureMalformed } - var v = Int32(v) + let v = Int32(v) - for i in 0..<(32 - r.count) { + for _ in 0..<(32 - r.count) { r.insert(0, at: 0) } - for i in 0..<(32 - s.count) { + for _ in 0..<(32 - s.count) { s.insert(0, at: 0) } @@ -286,7 +295,7 @@ public final class EthereumPublicKey { throw Error.internalError } return secp256k1_ecdsa_verify(ctx, sig, &hash, pubkey) == 1 - }*/ + } /** * Returns this public key serialized as a hex string. diff --git a/Tests/Web3Tests/TransactionTests/EthereumPublicKeyTests.swift b/Tests/Web3Tests/TransactionTests/EthereumPublicKeyTests.swift index ebe5edf7..cec6c871 100644 --- a/Tests/Web3Tests/TransactionTests/EthereumPublicKeyTests.swift +++ b/Tests/Web3Tests/TransactionTests/EthereumPublicKeyTests.swift @@ -91,6 +91,69 @@ class EthereumPublicKeyTests: QuickSpec { expect(pub1?.hashValue) != pub2?.hashValue } } + + context("Public key generation from elliptic curve") { + let rlpDecoder = RLPDecoder() + + it("should correctly verify the signature") { + let rawTx = "0xf8710180830f4240850d5bd0dff6825208944f5e9d9e6e05af22ef7683548c1c67a0436ea86987133a618ca1c85080c001a0f71d478c03498d090cf00e80f1bf4bba753dcb06fdcd5b0a0d683adb19a9ee5aa04aaa7c9720b1e3e13af27e20f489fa3ad4668ef5d4786176e47453192c4912e1".hexToBytes() + let rlpArray = try? rlpDecoder.decode(rawTx) + let tx = try! EthereumSignedTransaction(rlp: rlpArray!) + + let rlp = RLPItem( + nonce: tx.nonce, + gasPrice: tx.gasPrice, + maxFeePerGas: tx.maxFeePerGas, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas, + gasLimit: tx.gasLimit, + to: tx.to, + value: tx.value, + data: tx.data, + chainId: tx.chainId, + accessList: tx.accessList, + transactionType: tx.transactionType + ) + let rawRlp = try RLPEncoder().encode(rlp) + var messageToSign = Bytes() + messageToSign.append(0x02) + messageToSign.append(contentsOf: rawRlp) + + let originalPublicKey = try EthereumPublicKey(hexPublicKey: "0xcca03e481ecd3c7fe43bc5e3c495a8601557c52c509d9ca7fc89d3052a9855209a2a73b7f929e664baf24abb8443ebbd2ae7ef5bce3741ec013b721a545c136f") + + expect(originalPublicKey.address.hex(eip55: true)) == "0x3d4CE0e38A3F4294df8AE65bC8A57b8eEc976203" + + let isCorrect = try originalPublicKey.verifySignature(message: messageToSign, v: tx.v.makeBytes().bigEndianUInt!, r: tx.r.quantity, s: tx.s.quantity) + expect(isCorrect) == true + } + + it("should generate the correct from address") { + let rawTx = "0xf8710180830f4240850d5bd0dff6825208944f5e9d9e6e05af22ef7683548c1c67a0436ea86987133a618ca1c85080c001a0f71d478c03498d090cf00e80f1bf4bba753dcb06fdcd5b0a0d683adb19a9ee5aa04aaa7c9720b1e3e13af27e20f489fa3ad4668ef5d4786176e47453192c4912e1".hexToBytes() + let rlpArray = try? rlpDecoder.decode(rawTx) + let tx = try! EthereumSignedTransaction(rlp: rlpArray!) + + let rlp = RLPItem( + nonce: tx.nonce, + gasPrice: tx.gasPrice, + maxFeePerGas: tx.maxFeePerGas, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas, + gasLimit: tx.gasLimit, + to: tx.to, + value: tx.value, + data: tx.data, + chainId: tx.chainId, + accessList: tx.accessList, + transactionType: tx.transactionType + ) + let rawRlp = try RLPEncoder().encode(rlp) + var messageToSign = Bytes() + messageToSign.append(0x02) + messageToSign.append(contentsOf: rawRlp) + + let from = try EthereumPublicKey(message: messageToSign, v: tx.v, r: tx.r, s: tx.s).address + + expect(from.hex(eip55: true)) == "0x3d4CE0e38A3F4294df8AE65bC8A57b8eEc976203" + } + } } } } diff --git a/Tests/Web3Tests/Web3Tests/Web3EventsTests.swift b/Tests/Web3Tests/Web3Tests/Web3EventsTests.swift index 14d6a3bb..4459881c 100644 --- a/Tests/Web3Tests/Web3Tests/Web3EventsTests.swift +++ b/Tests/Web3Tests/Web3Tests/Web3EventsTests.swift @@ -43,7 +43,7 @@ class Web3EventsTests: QuickSpec { it("should subscribe and unsubscribe to new heads") { waitUntil(timeout: .seconds(30)) { done in var subId = "" - var cancelled = NIOLockedValueBox(false) + let cancelled = NIOLockedValueBox(false) try! web3Ws.eth.subscribeToNewHeads(subscribed: { response in expect(response.result).toNot(beNil()) @@ -85,7 +85,7 @@ class Web3EventsTests: QuickSpec { it("should subscribe and unsubscribe to new pending transactions") { waitUntil(timeout: .seconds(5)) { done in var subId = "" - var cancelled = NIOLockedValueBox(false) + let cancelled = NIOLockedValueBox(false) try! web3Ws.eth.subscribeToNewPendingTransactions(subscribed: { response in expect(response.result).toNot(beNil()) @@ -129,7 +129,7 @@ class Web3EventsTests: QuickSpec { it("should subscribe and unsubscribe to all logs") { waitUntil(timeout: .seconds(60)) { done in var subId = "" - var cancelled = NIOLockedValueBox(false) + let cancelled = NIOLockedValueBox(false) try! web3Ws.eth.subscribeToLogs(subscribed: { response in expect(response.result).toNot(beNil()) @@ -172,7 +172,7 @@ class Web3EventsTests: QuickSpec { // We test USDT transfers as they happen basically every block waitUntil(timeout: .seconds(60)) { done in var subId = "" - var cancelled = NIOLockedValueBox(false) + let cancelled = NIOLockedValueBox(false) try! web3Ws.eth.subscribeToLogs( addresses: [EthereumAddress(hex: "0xdAC17F958D2ee523a2206206994597C13D831ec7", eip55: false )], topics: [[EthereumData(ethereumValue: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")]],