Skip to content

Commit

Permalink
fix: ethereum public key constructor from message and signature (#171)
Browse files Browse the repository at this point in the history
fix: ethereum public key constructor from message and signature (recover) fix
  • Loading branch information
koraykoska authored Feb 20, 2024
1 parent 6b67d7b commit 1aaf220
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 13 deletions.
6 changes: 3 additions & 3 deletions Sources/Core/Providers/Web3Provider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public struct Web3Response<Result: Codable> {
case subscriptionCancelled(Swift.Error?)
}

public enum Status<Result> {
case success(Result)
public enum Status<StatusResult> {
case success(StatusResult)
case failure(Swift.Error)
}

Expand Down Expand Up @@ -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
Expand Down
21 changes: 15 additions & 6 deletions Sources/Core/Transaction/EthereumPublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
}

/**
Expand All @@ -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
Expand All @@ -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)
}

Expand Down Expand Up @@ -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.
Expand Down
63 changes: 63 additions & 0 deletions Tests/Web3Tests/TransactionTests/EthereumPublicKeyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
}
8 changes: 4 additions & 4 deletions Tests/Web3Tests/Web3Tests/Web3EventsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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")]],
Expand Down

0 comments on commit 1aaf220

Please sign in to comment.