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

fix: ethereum public key constructor from message and signature #171

Merged
merged 1 commit into from
Feb 20, 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
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
Loading