Skip to content

Commit

Permalink
Add Ed25519 private and public key structs
Browse files Browse the repository at this point in the history
  • Loading branch information
qtbeee committed Sep 21, 2019
1 parent 232c312 commit 2f16415
Show file tree
Hide file tree
Showing 64 changed files with 14,864 additions and 22 deletions.
4 changes: 4 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
included:
- Sources
excluded:
- Sources/hedera/protobuf
34 changes: 34 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"object": {
"pins": [
{
"package": "Clibsodium",
"repositoryURL": "https://github.com/tiwoc/Clibsodium.git",
"state": {
"branch": null,
"revision": "2ac72772aae26b2da0b15c253d9bb79ba452f66a",
"version": "1.0.0"
}
},
{
"package": "SwiftProtobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf",
"state": {
"branch": null,
"revision": "3a3594f84b746793c84c2ab2f1e855aaa9d3a593",
"version": "1.6.0"
}
},
{
"package": "Sodium",
"repositoryURL": "https://github.com/jedisct1/swift-sodium",
"state": {
"branch": null,
"revision": "5900a54dea817befa18f3148889fe922a38afe7f",
"version": "0.8.0"
}
}
]
},
"version": 1
}
9 changes: 6 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ import PackageDescription

let package = Package(
name: "hedera",
platforms: [
.macOS(.v10_13),
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "hedera",
targets: ["hedera"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/jedisct1/swift-sodium", from: "0.8.0"),
.package(url: "https://github.com/apple/swift-protobuf", from: "1.6.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "hedera",
dependencies: []),
dependencies: ["Sodium", "SwiftProtobuf"]),
.testTarget(
name: "hederaTests",
dependencies: ["hedera"]),
Expand Down
25 changes: 25 additions & 0 deletions Sources/hedera/crypto/PublicKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation
import Sodium

// TODO
public struct HederaError: Error {}

public protocol PublicKey {
func toProtoKey() -> Proto_Key
}

extension PublicKey {
// static func from(proto key: Proto_Key) throws -> PublicKey {
// switch key.key! {
// case let .ed25519(data):
// return try! Ed25519PublicKey.from(bytes: Bytes(data))
// // TODO: case .contractID()
// default:
// // TODO: Unhandled Key Case error
// throw HederaError()
// }
// }

// TODO
// static func from(string: String) -> PublicKey {}
}
31 changes: 31 additions & 0 deletions Sources/hedera/crypto/Utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Sodium

let sodium = Sodium()

struct HexDecodeError: Error {}

func hexDecode<S>(_ hex: S) -> Result<Bytes, HexDecodeError> where S: StringProtocol {
if hex.count % 2 != 0 {
return .failure(HexDecodeError())
}

let bytesLen = hex.count / 2
var bytes = Bytes(repeating: 0, count: bytesLen)

for index in 0 ..< bytesLen {
let start = hex.index(hex.startIndex, offsetBy: index * 2)
let end = hex.index(start, offsetBy: 2)
bytes[index] = UInt8(hex[start ..< end], radix: 16)!
}

return .success(bytes)
}

func hexEncode<S>(bytes: Bytes, prefixed with: S) -> String where S: StringProtocol {
var result = String(with)
for byte in bytes {
result.append(String(format: "%02x", byte))
}

return result
}
71 changes: 70 additions & 1 deletion Sources/hedera/crypto/ed25519/Ed25519PrivateKey.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,72 @@
// TODO
import Sodium

let ed25519PrivateKeyPrefix = "302e020100300506032b657004220420"
let ed25519PrivateKeyLength = 32
let combinedEd25519KeyLength = 64

// TODO: how to handle error possibilities???
struct InvalidKeyBytes: Error {}

public struct Ed25519PrivateKey {
var inner: Bytes

private init(bytes: Bytes) {
inner = bytes
}

public static func from(bytes: Bytes) -> Result<Ed25519PrivateKey, Error> {
if bytes.count == ed25519PrivateKeyLength {
return .success(Ed25519PrivateKey(bytes: bytes))
} else if bytes.count == combinedEd25519KeyLength {
return .success(Ed25519PrivateKey(bytes: Bytes(bytes.prefix(ed25519PrivateKeyLength))))
}
return .failure(InvalidKeyBytes())
}

var bytes: Bytes {
return inner
}

public static func generate() -> Ed25519PrivateKey {
return Ed25519PrivateKey(bytes: sodium.box.keyPair()!.secretKey)
}

public func getPublicKey() -> Ed25519PublicKey {
return try! Ed25519PublicKey.from(bytes: sodium.box.keyPair(seed: inner)!.publicKey).get()
}
}

extension Ed25519PrivateKey: CustomStringConvertible {
public var description: String {
return hexEncode(bytes: inner, prefixed: ed25519PrivateKeyPrefix)
}
}

extension Ed25519PrivateKey: CustomDebugStringConvertible {
public var debugDescription: String {
return description
}
}

extension Ed25519PrivateKey: LosslessStringConvertible {
// Recover from a hex encoded string. Does not support key derivation.
public init?(_ description: String) {
switch description.count {
case ed25519PrivateKeyLength * 2, combinedEd25519KeyLength * 2: // lone key, or combined key
// This cannot fail to decode
// swiftlint:disable:next force_try
inner = try! Ed25519PrivateKey.from(bytes: try! hexDecode(description).get()).get().inner
case ed25519PrivateKeyLength * 2 + ed25519PrivateKeyPrefix.count: // DER encoded key
if description.hasPrefix(ed25519PrivateKeyPrefix) {
let range = description.index(description.startIndex, offsetBy: ed25519PrivateKeyPrefix.count)...
// This cannot fail to decode
// swiftlint:disable:next force_try
inner = try! Ed25519PrivateKey.from(bytes: try! hexDecode(description[range]).get()).get().inner
} else {
return nil
}
default:
return nil
}
}
}
63 changes: 62 additions & 1 deletion Sources/hedera/crypto/ed25519/Ed25519PublicKey.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,64 @@
// TODO
import Sodium
import Foundation

let ed25519PublicKeyPrefix = "302a300506032b6570032100"
let ed25519PublicKeyLength = 32

public struct Ed25519PublicKey {
let inner: Bytes

private init(bytes: Bytes) {
inner = bytes
}

static func from(bytes: Bytes) -> Result<Ed25519PublicKey, Error> {
if bytes.count == ed25519PublicKeyLength {
return .success(Ed25519PublicKey(bytes: bytes))
} else {
// TODO: actual error "invalid public key"
return .failure(HederaError())
}
}

var bytes: Bytes {
return inner
}
}

extension Ed25519PublicKey: PublicKey {
public func toProtoKey() -> Proto_Key {
var proto = Proto_Key()
proto.ed25519 = Data(inner)
return proto
}
}

extension Ed25519PublicKey: CustomStringConvertible {
public var description: String {
return hexEncode(bytes: inner, prefixed: ed25519PublicKeyPrefix)
}
}

extension Ed25519PublicKey: CustomDebugStringConvertible {
public var debugDescription: String {
return description
}
}

extension Ed25519PublicKey: LosslessStringConvertible {
public init?(_ description: String) {
switch description.count {
case ed25519PublicKeyLength * 2:
// This cannot fail
// swiftlint:disable:next force_try
inner = try! hexDecode(description).get()
case ed25519PublicKeyLength * 2 + ed25519PublicKeyPrefix.count:
let start = description.index(description.startIndex, offsetBy: ed25519PublicKeyPrefix.count)
// This cannot fail
// swiftlint:disable:next force_try
inner = try! hexDecode(description[start...]).get()
default:
return nil
}
}
}
Loading

0 comments on commit 2f16415

Please sign in to comment.