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

Get PublicKey<Secp256k1> from Data #22

Open
grill2010 opened this issue Apr 24, 2021 · 9 comments
Open

Get PublicKey<Secp256k1> from Data #22

grill2010 opened this issue Apr 24, 2021 · 9 comments

Comments

@grill2010
Copy link

Hi,

I'm currently porting my Android app to iOS and I stumbled across your library which seems to perfectly meet my requirements. I'm not sure if I do something wrong but I'm not able to create a public key from Data. I have created a Unit test to check if the output of some crypto functions is the same as in my Android app. Therefore I use following uncompressed public key. Below the uncompressed public key in hex representation

04F40AF135A4889436CEE52B5C73A33EC5AD0BE0952F57F4F0ED0C80B0BEDA7CA643789393A5947E9FAA3F6795C9AA09A96325DFE850BFC3F1DB62A50ABFB0FFF7

Once converted the hex string to Data I use following function calls

let decodedFromUncompressed = try AffinePoint<Secp256k1>.init(data: publicKeyBytes)
return PublicKey<Secp256k1>.init(point: decodedFromUncompressed)

This leads to an error here

Upon further investigation I saw that the call

let yData = y.as256bitLongData()

returned 0 despite the value is not 0. It seems the as256bitLongData uses internally a function called asHexStringLength64 which returns following strange hex value

0-789393A5947E9FAA3F6795C9AA09A96325DFE850BFC3F1DB62A50ABFB0FFF7

The function fills up the missing values with 0 but seems to ignore the '-' sign. Is this the root cause of the error, did I misunderstand something or is there another way to get a PublicKey from uncompressed Data?

@grill2010
Copy link
Author

grill2010 commented Apr 25, 2021

One more thing I noticed.

I create an AffinePoint with the raw Data like shown in my message above (first hex value) like this

let decodedFromUncompressed = try AffinePoint<Secp256k1>.init(data: publicKeyBytes)

I wanted to check the x and y values. I tried to recreate the original hex value like above. Therfore I didn't use the as256bitLongData as it doesn't correctly interpret signed values. I did it like that

        let x = decodedFromUncompressed.x
        let y = decodedFromUncompressed.y

        let xData = x.serialize()
        let yData = y.serialize()

        let uncompressedPrefix = Data(0x04)
        let uncompressed = [uncompressedPrefix, xData, yData]

        let uncompressedData: Data = uncompressed.reduce(.empty, +)
        let testCheck = HexUtil.hexlify(uncompressedData)

which gave me following result

04000af135a4889436cee52b5c73a33ec5ad0be0952f57f4f0ed0c80b0beda7ca601789393a5947e9faa3f6795c9aa09a96325dfe850bfc3f1db62a50abfb0fff7

The reason for this is that it uses following calls internally in init function of AffinePoint

        x: .init(bytes.subdata(in: 1..<33)),
        y: .init(bytes.subdata(in: 33..<65))

which is calling the BigInt extension method in Data Conversion.swift from Károly Lőrentey which accordingly to the documentation always drops the first byte

        /// Initializes an integer from the bits stored inside a piece of `Data`.
        /// The data is assumed to be in network (big-endian) byte order with a first
        /// byte to represent the sign (0 for positive, 1 for negative)

Which means my AffinePoint will actually never represent the raw data I pass via the init function...


Edit: it actually works when I create a PublicKey from my uncompressed raw Data public key like this

        let xPart = publicKeyBytes.subdata(in: 1..<33)
        let yPart = publicKeyBytes.subdata(in: 33..<65)
        let __X: BigInt = .init(Data([0] + xPart))
        let __Y: BigInt = .init(Data([0] + yPart))
        let decodedFromUncompressed = try AffinePoint<Secp256k1>.init(x: __X, y: __Y)
        return PublicKey<Secp256k1>.init(point: decodedFromUncompressed)

This workaround works for me at the moment but I think the stuff above I mentioned before is not as it should be. Sorry for spamming, waiting for your feedback about this @Sajjon :)

@Sajjon
Copy link
Owner

Sajjon commented Apr 27, 2021

@grill2010 thx for reporting this issue, I'm making a fix now, stay tuned.

@Sajjon
Copy link
Owner

Sajjon commented Apr 28, 2021

@grill2010 Hmm I was unable to reproduce, which version of EllipticCurveKit are you usign? on main bracnh I wrote this test:

import Foundation
import XCTest
@testable import EllipticCurveKit

final class PublicKeyFromDataTests: XCTestCase {
    func testPubKeyFromData() throws {
        let pubKeyUncompressedHex = "04f40af135a4889436cee52b5c73a33ec5ad0be0952f57f4f0ed0c80b0beda7ca643789393a5947e9faa3f6795c9aa09a96325dfe850bfc3f1db62a50abfb0fff7"
        let pubKeyUncompressedData = Data(hex: pubKeyUncompressedHex)
        XCTAssertEqual(pubKeyUncompressedHex, pubKeyUncompressedData.toHexString())
        
        let affinePoint = try AffinePoint<Secp256k1>(data: pubKeyUncompressedData)
        
        let expectedY = "43789393a5947e9faa3f6795c9aa09a96325dfe850bfc3f1db62a50abfb0fff7"
        let expectedX = "f40af135a4889436cee52b5c73a33ec5ad0be0952f57f4f0ed0c80b0beda7ca6"
        
        XCTAssertEqual(affinePoint.y.asHexString(uppercased: false), expectedY)
        XCTAssertEqual(affinePoint.x.asHexString(uppercased: false), expectedX)
        
        let publickey = PublicKey<Secp256k1>.init(point: affinePoint)
        
        XCTAssertEqual(publickey.y.asHexString(uppercased: false), expectedY)
        XCTAssertEqual(publickey.x.asHexString(uppercased: false), expectedX)
        
    }

}

Which works just fine. But I did notice the issue with leading minus sign when converting BigInt to hexstring. Wonder if it might be safe to just drop that minus sign. Will have to investigate the implications of this.

@grill2010
Copy link
Author

grill2010 commented Apr 28, 2021

I use the main branch of EllipticCurveKit.

Just dropping the minus sign is probably not safe as the reason for this problem is in AffinePoint. E.g.

let decodedFromUncompressed = try AffinePoint<Secp256k1>.init(data: publicKeyBytes)

In the constructor of AffinePoint it uses following BigInt init functions to generate x and y

x: .init(bytes.subdata(in: 1..<33)),
y: .init(bytes.subdata(in: 33..<65))

and the BigInt from Károly Lőrentey assumes the first byte represents the sign (0 for positive, 1 for negative). So this leads to following problems, it always drops the first byte for the BigInt value which is bad because it doesn't represent then the correct value in AffinePoint and second when the first byte is not 0 but any other value the BigInt is treated as negative BigInt which then leads to the problem in PublicKey as the values should never be negative for BigInt.

Edit: I copied your test and it give me the exact same error ^^ Don't know why this doesn't happen on your side, it's strange.

@grill2010
Copy link
Author

At least these lines should fail on your side as well

    XCTAssertEqual(affinePoint.y.asHexString(uppercased: false), expectedY)
    XCTAssertEqual(affinePoint.x.asHexString(uppercased: false), expectedX)


Screenshot 2021-04-28 at 10 22 06

@grill2010
Copy link
Author

@Sajjon any news in regards this topic?
It works fine on my side with the described workarounds but i'm just curious what your results are?

@ghost
Copy link

ghost commented Jun 12, 2022

@Sajjon any news in regards this topic?
It works fine on my side with the described workarounds but i'm just curious what your results are?

I encountered the same issue as yours. Could you tell me how you fixed it??

@Sajjon
Copy link
Owner

Sajjon commented Jun 12, 2022

Hey! Did you guys try decoding an AffinePoint from data and pass that to PublicKey init?

72621e1

Otherwise can you pass me the test PrivateKey, expected public key and I can add a test and have a look if it fails.

@grill2010
Copy link
Author

@EunJuJang sorry for the late reply, I use the workaround mentioned in the this comment above

#22 (comment)

@Sajjon the tests I posted above fail for me every time if I don't use the workaround mentioned above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants