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

Replace 1.4 by pure secp256k1 implementation #79

Merged
merged 6 commits into from
Mar 8, 2023
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
36 changes: 25 additions & 11 deletions AtalaPrismSDK/Apollo/Sources/ApolloImpl+Public.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ returns random mnemonics nerver returns invalid mnemonics
/// - seed: A seed object used to generate the key pair
/// - curve: The key curve to use for generating the key pair
/// - Returns: A key pair object containing a private and public key
public func createKeyPair(seed: Seed, curve: KeyCurve) -> KeyPair {
public func createKeyPair(seed: Seed, curve: KeyCurve) throws -> KeyPair {
switch curve {
case .x25519:
return CreateX25519KeyPairOperation(logger: ApolloImpl.logger)
Expand All @@ -51,7 +51,7 @@ returns random mnemonics nerver returns invalid mnemonics
return CreateEd25519KeyPairOperation(logger: ApolloImpl.logger)
.compute()
case let .secp256k1(index):
return CreateSec256k1KeyPairOperation(
return try CreateSec256k1KeyPairOperation(
logger: ApolloImpl.logger,
seed: seed,
keyPath: .init(index: index)
Expand All @@ -69,7 +69,7 @@ returns random mnemonics nerver returns invalid mnemonics
public func createKeyPair(seed: Seed, privateKey: PrivateKey) throws -> KeyPair {
switch privateKey.curve {
case .secp256k1:
return createKeyPair(seed: seed, curve: privateKey.curve)
return try createKeyPair(seed: seed, curve: privateKey.curve)
case .x25519:
return try CreateX25519KeyPairOperation(logger: ApolloImpl.logger)
.compute(fromPrivateKey: privateKey)
Expand All @@ -84,15 +84,25 @@ returns random mnemonics nerver returns invalid mnemonics
/// - Parameter publicKey: The public key to compress
/// - Returns: The compressed public key
public func compressedPublicKey(publicKey: PublicKey) -> CompressedPublicKey {
publicKey.compressed()
CompressedPublicKey(
uncompressed: publicKey,
value: LockPublicKey(
bytes: publicKey.value
).compressedPublicKey().data
)
}

/// compressedPublicKey decompresses a given compressed public key into its original form.
///
/// - Parameter compressedData: The compressed public key data
/// - Returns: The decompressed public key
public func compressedPublicKey(compressedData: Data) -> CompressedPublicKey {
CompressedPublicKey(compressedData: compressedData)
public func uncompressedPublicKey(compressedData: Data) -> PublicKey {
PublicKey(
curve: KeyCurve.secp256k1().name,
value: LockPublicKey(
bytes: compressedData
).uncompressedPublicKey().data
)
}

/// signMessage signs a message using a given private key, returning the signature.
Expand All @@ -101,8 +111,8 @@ returns random mnemonics nerver returns invalid mnemonics
/// - privateKey: The private key to use for signing the message
/// - message: The message to sign, in binary data form
/// - Returns: The signature of the message
public func signMessage(privateKey: PrivateKey, message: Data) -> Signature {
return SignMessageOperation(
public func signMessage(privateKey: PrivateKey, message: Data) throws -> Signature {
return try SignMessageOperation(
logger: ApolloImpl.logger,
privateKey: privateKey,
message: message
Expand All @@ -118,7 +128,7 @@ returns random mnemonics nerver returns invalid mnemonics
/// - Throws: An error if the message is invalid
public func signMessage(privateKey: PrivateKey, message: String) throws -> Signature {
guard let data = message.data(using: .utf8) else { throw ApolloError.couldNotParseMessageString }
return signMessage(privateKey: privateKey, message: data)
return try signMessage(privateKey: privateKey, message: data)
}

/// verifySignature verifies the authenticity of a signature using the corresponding public key, challenge, and signature. This function returns a boolean value indicating whether the signature is valid or not.
Expand All @@ -128,8 +138,12 @@ returns random mnemonics nerver returns invalid mnemonics
/// - challenge: The challenge used to generate the signature
/// - signature: The signature to verify
/// - Returns: A boolean value indicating whether the signature is valid or not
public func verifySignature(publicKey: PublicKey, challenge: Data, signature: Signature) -> Bool {
return VerifySignatureOperation(
public func verifySignature(
publicKey: PublicKey,
challenge: Data,
signature: Signature
) throws -> Bool {
return try VerifySignatureOperation(
logger: ApolloImpl.logger,
publicKey: publicKey,
challenge: challenge,
Expand Down
101 changes: 101 additions & 0 deletions AtalaPrismSDK/Apollo/Sources/BIPs/BIP32_44/HDKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// BitcoinKitPrivateSwift.swift
//
// Copyright © 2019 BitcoinKit developers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Modified by Gonçalo Frade on 07/03/2023
//
// Changes made:
// - Moved to a file of its own
// - Removed Fingerprints and Network
// - Removed Public

import CryptoKit
import Foundation
import secp256k1

class HDKey {
private(set) var privateKey: Data?
private(set) var publicKey: Data
private(set) var chainCode: Data
private(set) var depth: UInt8
private(set) var childIndex: UInt32

init(privateKey: Data?, publicKey: Data, chainCode: Data, depth: UInt8, childIndex: UInt32) {
self.privateKey = privateKey
self.publicKey = publicKey
self.chainCode = chainCode
self.depth = depth
self.childIndex = childIndex
}

func derived(at childIndex: UInt32, hardened: Bool) -> HDKey? {
var data = Data()
if hardened {
data.append(0)
guard let privateKey = self.privateKey else {
return nil
}
data.append(privateKey)
} else {
data.append(publicKey)
}
var childIndex = CFSwapInt32HostToBig(hardened ? (0x80000000 as UInt32) | childIndex : childIndex)
data.append(Data(bytes: &childIndex, count: MemoryLayout<UInt32>.size))
let hmac = HMAC<SHA512>.authenticationCode(for: data, using: .init(data: self.chainCode))
let digest = Data(hmac)
let derivedPrivateKey: [UInt8] = digest[0..<32].map { $0 }
let derivedChainCode: [UInt8] = digest[32..<64].map { $0 }
var result: Data
if let privateKey = self.privateKey {
guard let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN)) else {
return nil
}
defer { secp256k1_context_destroy(ctx) }
var privateKeyBytes = privateKey.map { $0 }
var derivedPrivateKeyBytes = derivedPrivateKey.map { $0 }
if secp256k1_ec_seckey_tweak_add(ctx, &privateKeyBytes, &derivedPrivateKeyBytes) == 0 {
return nil
}
result = Data(privateKeyBytes)
} else {
guard let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_VERIFY)) else {
return nil
}
defer { secp256k1_context_destroy(ctx) }
let publicKeyBytes: [UInt8] = publicKey.map { $0 }
var secpPubkey = secp256k1_pubkey()
if secp256k1_ec_pubkey_parse(ctx, &secpPubkey, publicKeyBytes, publicKeyBytes.count) == 0 {
return nil
}
if secp256k1_ec_pubkey_tweak_add(ctx, &secpPubkey, derivedPrivateKey) == 0 {
return nil
}
var compressedPublicKeyBytes = [UInt8](repeating: 0, count: 33)
var compressedPublicKeyBytesLen = 33
if secp256k1_ec_pubkey_serialize(ctx, &compressedPublicKeyBytes, &compressedPublicKeyBytesLen, &secpPubkey, UInt32(SECP256K1_EC_COMPRESSED)) == 0 {
return nil
}
result = Data(compressedPublicKeyBytes)
}
return HDKey(privateKey: result, publicKey: result, chainCode: Data(derivedChainCode), depth: self.depth + 1, childIndex: childIndex)
}
}
90 changes: 90 additions & 0 deletions AtalaPrismSDK/Apollo/Sources/BIPs/BIP32_44/HDKeychain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// HDKeychain.swift
//
// Copyright © 2018 Kishikawa Katsumi
// Copyright © 2018 BitcoinKit developers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Modified by Gonçalo Frade on 07/03/2023
//
// Changes made:
// - Using CryptoKit now
// - Changed to a struct instead of a class
// - Removed Fingerprints and Network
// - Removed Public

import CryptoKit
import Foundation
import secp256k1

struct HDKeychain {
private let rootKey: HDPrivateKey

init(rootKey: HDPrivateKey) {
self.rootKey = rootKey
}

init(seed: Data) {
self.init(rootKey: HDPrivateKey(seed: seed))
}
/// Parses the BIP32 path and derives the chain of keychains accordingly.
/// Path syntax: (m?/)?([0-9]+'?(/[0-9]+'?)*)?
/// The following paths are valid:
///
/// "" (root key)
/// "m" (root key)
/// "/" (root key)
/// "m/0'" (hardened child #0 of the root key)
/// "/0'" (hardened child #0 of the root key)
/// "0'" (hardened child #0 of the root key)
/// "m/44'/1'/2'" (BIP44 testnet account #2)
/// "/44'/1'/2'" (BIP44 testnet account #2)
/// "44'/1'/2'" (BIP44 testnet account #2)
///
/// The following paths are invalid:
///
/// "m / 0 / 1" (contains spaces)
/// "m/b/c" (alphabetical characters instead of numerical indexes)
/// "m/1.2^3" (contains illegal characters)
func derivedKey(path: String) throws -> HDPrivateKey {
var key = rootKey

var path = path
if path == "m" || path == "/" || path == "" {
return key
}
if path.contains("m/") {
path = String(path.dropFirst(2))
}
for chunk in path.split(separator: "/") {
var hardened = false
var indexText = chunk
if chunk.contains("'") {
hardened = true
indexText = indexText.dropLast()
}
guard let index = UInt32(indexText) else {
fatalError("invalid path")
}
key = try key.derived(at: index, hardened: hardened)
}
return key
}
}
94 changes: 94 additions & 0 deletions AtalaPrismSDK/Apollo/Sources/BIPs/BIP32_44/HDPrivateKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// DeterministicKey.swift
//
// Copyright © 2018 Kishikawa Katsumi
// Copyright © 2018 BitcoinKit developers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Modified by Gonçalo Frade on 07/03/2023
//
// Changes made:
// - Using CryptoKit now
// - Changed to a struct instead of a class
// - Removed Fingerprints and Network
// - Removed public

import CryptoKit
import Foundation
import secp256k1

struct HDPrivateKey {
let depth: UInt8
let childIndex: UInt32

let raw: Data
let chainCode: Data

init(privateKey: Data, chainCode: Data) {
self.raw = privateKey
self.chainCode = chainCode
self.depth = 0
self.childIndex = 0
}

init(seed: Data) {
let hmac = HMAC<SHA512>.authenticationCode(for: seed, using: .init(data: "Bitcoin seed".data(using: .ascii)!))
let hmacData = Data(hmac)
// let hmac = Crypto.hmacsha512(data: seed, key: "Bitcoin seed".data(using: .ascii)!)
let privateKey = hmacData[0..<32]
let chainCode = hmacData[32..<64]
self.init(privateKey: privateKey, chainCode: chainCode)
}

init(privateKey: Data, chainCode: Data, depth: UInt8, childIndex: UInt32) {
self.raw = privateKey
self.chainCode = chainCode
self.depth = depth
self.childIndex = childIndex
}

func privateKey() -> LockPrivateKey {
return LockPrivateKey(data: raw, isPublicKeyCompressed: true)
}

func extendedPublicKey() -> HDPublicKey {
return HDPublicKey(raw: computePublicKeyData(), chainCode: chainCode, depth: depth, childIndex: childIndex)
}

private func computePublicKeyData() -> Data {
return KeyHelpers.computePublicKey(fromPrivateKey: raw, compression: false)
}

func derived(at index: UInt32, hardened: Bool = false) throws -> HDPrivateKey {
// As we use explicit parameter "hardened", do not allow higher bit set.
if (0x80000000 & index) != 0 {
fatalError("invalid child index")
}

guard let derivedKey = HDKey(privateKey: raw, publicKey: extendedPublicKey().raw, chainCode: chainCode, depth: depth, childIndex: childIndex).derived(at: index, hardened: hardened) else {
throw DerivationError.derivationFailed
}
return HDPrivateKey(privateKey: derivedKey.privateKey!, chainCode: derivedKey.chainCode, depth: derivedKey.depth, childIndex: derivedKey.childIndex)
}
}

enum DerivationError: Error {
case derivationFailed
}
Loading