From 1f0b17e10ae4427966de48c0cd646c3f7028e0e7 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 12:15:36 -0600 Subject: [PATCH 01/33] Guards and helpers for compressed public key --- Sources/XyoClient/Wallet/Wallet.swift | 71 ++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index 6814f1d..92a8985 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -11,19 +11,26 @@ public struct Key { public enum WalletError: Error { case failedToGetPublicKey - case failedToDervivePath + case failedToDerivePath case invalidSeed case invalidPath case invalidPathComponent case invalidSeedLength case failedToGenerateHmac case invalidPrivateKeyLength + case invalidChildKey + case missingPrivateKey + case missingPublicKey } public class Wallet: Account, WalletInstance { static let defaultPath = "m/44'/60'/0'/0/0" + // Define the secp256k1 curve order + static let secp256k1CurveOrder = BigInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! + + public func derivePath(path: String) throws -> any WalletInstance { let key = try Wallet.deriveKey(from: self._key, path: path) return try Wallet(key: key) @@ -59,43 +66,81 @@ public class Wallet: Account, WalletInstance { let hardened = component.last == "'" let indexString = hardened ? component.dropLast() : component guard let index = UInt32(indexString) else { - throw WalletError.invalidPathComponent + throw WalletError.invalidPathComponent } let derivedIndex = hardened ? index | 0x80000000 : index - let childKey = deriveChildKey(parentKey: currentKey, index: derivedIndex) - currentKey = childKey + currentKey = try deriveChildKey(parentKey: currentKey, index: derivedIndex) } return currentKey } /// Derives a child key given a parent key and an index - private static func deriveChildKey(parentKey: Key, index: UInt32) -> Key { + private static func deriveChildKey(parentKey: Key, index: UInt32) throws -> Key { var data = Data() if index >= 0x80000000 { // Hardened key: prepend 0x00 and parent private key + guard parentKey.privateKey.count == 32 else { + throw WalletError.invalidPrivateKeyLength + } data.append(0x00) data.append(parentKey.privateKey) } else { - // Normal key: use the public key - let publicKey = getCompressedPublicKey(privateKey: parentKey.privateKey) + // Normal key: use the compressed public key + guard let publicKey = try? getCompressedPublicKey(privateKey: parentKey.privateKey) else { + throw WalletError.failedToGetPublicKey + } + let val = publicKey.toHex() data.append(publicKey) } // Append the index data.append(contentsOf: withUnsafeBytes(of: index.bigEndian, Array.init)) - + + // Perform HMAC-SHA512 + guard data.count == 37 else { + throw WalletError.failedToGenerateHmac + } let hmac = Hmac.hmacSha512(key: parentKey.chainCode, data: data) - let derivedKeyData = Data(hmac) - let derivedPrivateKey = derivedKeyData.prefix(32) - let derivedChainCode = derivedKeyData.suffix(32) + let derivedPrivateKey = hmac.prefix(32) + let derivedChainCode = hmac.suffix(32) + + // Validate the derived private key + guard BigInt(Data(derivedPrivateKey)) < secp256k1CurveOrder else { + throw WalletError.invalidChildKey + } return Key(privateKey: derivedPrivateKey, chainCode: derivedChainCode) } - private static func getCompressedPublicKey(privateKey: Data) -> Data { - XyoAddress(privateKey: privateKey.toHexString()).publicKeyBytes! + private static func getCompressedPublicKey(privateKey: Data) throws -> Data { + guard let uncompressedKey = XyoAddress(privateKey: privateKey.toHexString()).publicKeyBytes else { + throw WalletError.failedToGetPublicKey + } +// return publicKeyBytes + // Ensure the input key is exactly 64 bytes + guard uncompressedKey.count == 64 else { + throw WalletError.invalidPrivateKeyLength + } + + // Extract x and y coordinates + let x = uncompressedKey.prefix(32) // First 32 bytes are x + let y = uncompressedKey.suffix(32) // Last 32 bytes are y + + // Convert y to an integer to determine parity + let yInt = BigInt(Data(y)) + let isEven = yInt % 2 == 0 + + // Determine the prefix based on the parity of y + let prefix: UInt8 = isEven ? 0x02 : 0x03 + + // Construct the compressed key: prefix + x + var compressedKey = Data([prefix]) // Start with the prefix + compressedKey.append(x) // Append the x-coordinate + + let test = compressedKey.toHexString() + return compressedKey } } From ae06cd9c9bff05bb0e6af37e1e53fa2c7ef9fbea Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 12:19:32 -0600 Subject: [PATCH 02/33] Add HMAC to dictionary --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index d663de3..1434793 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "Alamofire", "Arie", "boundwitness", + "hmac", "keccak", "Keychain", "Protobuf", From 35f22ab4c8b814d75bfe3b589164086ff9127789 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 13:47:31 -0600 Subject: [PATCH 03/33] Convert to BigInt from Hex --- Sources/XyoClient/Wallet/Wallet.swift | 52 ++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index 92a8985..d6cc14b 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -87,32 +87,67 @@ public class Wallet: Account, WalletInstance { } data.append(0x00) data.append(parentKey.privateKey) + print("Hardened Derivation: Prepended Private Key") } else { // Normal key: use the compressed public key guard let publicKey = try? getCompressedPublicKey(privateKey: parentKey.privateKey) else { throw WalletError.failedToGetPublicKey } - let val = publicKey.toHex() data.append(publicKey) + print("Non-Hardened Derivation: Appended Public Key") } // Append the index data.append(contentsOf: withUnsafeBytes(of: index.bigEndian, Array.init)) + print("Data for HMAC: \(data.toHex())") // Perform HMAC-SHA512 guard data.count == 37 else { throw WalletError.failedToGenerateHmac } let hmac = Hmac.hmacSha512(key: parentKey.chainCode, data: data) - let derivedPrivateKey = hmac.prefix(32) - let derivedChainCode = hmac.suffix(32) + let derivedPrivateKeyBytes = hmac.prefix(32) // Left 32 bytes (L) + let derivedChainCode = hmac.suffix(32) // Right 32 bytes (R) + + print("HMAC Output: \(Data(hmac).toHex())") + print("Derived Private Key Bytes (L): \(derivedPrivateKeyBytes.toHex())") + print("Derived Chain Code (R): \(derivedChainCode.toHex())") + + // Convert L to an integer + let L = BigInt(derivedPrivateKeyBytes.toHex(), radix: 16)! +// let curveOrder = BigInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! + print("Curve Order: \(secp256k1CurveOrder)") + print("L as BigInt: \(L)") + + // Validate L + guard L < secp256k1CurveOrder else { + throw WalletError.invalidChildKey + } - // Validate the derived private key - guard BigInt(Data(derivedPrivateKey)) < secp256k1CurveOrder else { + // Compute the child private key: (L + parentPrivateKey) % curveOrder +// let parentPrivateKeyInt = BigInt(Data(parentKey.privateKey)) + let parentPrivateKeyInt = BigInt(parentKey.privateKey.toHex(), radix: 16)! + let childPrivateKeyInt = (L + parentPrivateKeyInt) % secp256k1CurveOrder + print("Parent Private Key as Hex: \(parentKey.privateKey.toHexString())") + print("Parent Private Key as BigInt: \(parentPrivateKeyInt)") + print("Child Private Key as BigInt: \(childPrivateKeyInt)") + + // Ensure the child private key is valid + guard childPrivateKeyInt != 0 else { throw WalletError.invalidChildKey } - return Key(privateKey: derivedPrivateKey, chainCode: derivedChainCode) + // Convert the child private key back to Data + var childPrivateKey = childPrivateKeyInt.toData() + if childPrivateKey.count < 32 { + // Pad with leading zeros to make it 32 bytes + let padding = Data(repeating: 0, count: 32 - data.count) + childPrivateKey = padding + data + } + let test = childPrivateKey.toHexString() + + // Return the new child key + return Key(privateKey: childPrivateKey, chainCode: Data(derivedChainCode)) } private static func getCompressedPublicKey(privateKey: Data) throws -> Data { @@ -130,7 +165,7 @@ public class Wallet: Account, WalletInstance { let y = uncompressedKey.suffix(32) // Last 32 bytes are y // Convert y to an integer to determine parity - let yInt = BigInt(Data(y)) + let yInt = BigInt(y.toHex(), radix: 16)! let isEven = yInt % 2 == 0 // Determine the prefix based on the parity of y @@ -139,8 +174,7 @@ public class Wallet: Account, WalletInstance { // Construct the compressed key: prefix + x var compressedKey = Data([prefix]) // Start with the prefix compressedKey.append(x) // Append the x-coordinate - - let test = compressedKey.toHexString() + return compressedKey } } From 4b8e21eb0db90ee79c340f5b22425410f0c98370 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 14:00:44 -0600 Subject: [PATCH 04/33] Refactor test order --- Tests/XyoClientTests/WalletVectors.swift | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Tests/XyoClientTests/WalletVectors.swift b/Tests/XyoClientTests/WalletVectors.swift index 081b32b..eada50c 100644 --- a/Tests/XyoClientTests/WalletVectors.swift +++ b/Tests/XyoClientTests/WalletVectors.swift @@ -18,6 +18,7 @@ let testVectors: [TestVector] = [ path: "m/44'/60'/0'/0/0", address: "e46c258c74c7c1df33d7caa4c2c664dc0843ab3f", privateKey: "96a7705eedbb701a03ee235911253fd3eb80e48a06106c0bf957d42b72bd8efa", +// publicKey: "a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" publicKey: "03a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206" ) ] @@ -27,24 +28,20 @@ class WalletVectorTests: XCTestCase { func test_vectors() { do { let vector = testVectors[0] + + let wallet = try Wallet(phrase: vector.phrase, path: vector.path) + XCTAssertEqual(wallet.privateKey?.toHex(), vector.privateKey) + XCTAssertEqual(wallet.address?.toHex(), vector.address) + XCTAssertEqual(wallet.publicKey?.toHex(), vector.publicKey) + let entropy = try Bip39.mnemonicToEntropy(phrase: vector.phrase) let seed = try Bip39.entropyToSeed(entropy: entropy) - let seedString = seed.toHexString() let pk = try Bip39.rootPrivateKeyFromSeed(seed: seed) let pkDerived = try Wallet.deriveKey(from: pk, path: vector.path) - let pkString = pk.privateKey.toHexString() - let wallet = try Wallet(phrase: vector.phrase, path: vector.path) let wallet2 = try Wallet(key: pkDerived) - let wallet3 = try Wallet(key: Key(privateKey: dataFromHex(vector.privateKey)!, chainCode: Data())) - XCTAssertEqual(wallet.address?.toHex(), wallet2.address?.toHex()) - XCTAssertEqual(wallet.privateKey?.toHex(), wallet2.privateKey?.toHex()) - XCTAssertEqual(wallet.privateKey?.toHex(), vector.privateKey) - let calcAddress = wallet.address?.toHex() - let calcPrivateKey = wallet.privateKey?.toHex() - let calcPublicKey = wallet.publicKey?.toHex() - XCTAssertEqual(calcAddress, vector.address) - XCTAssertEqual(calcPrivateKey, vector.privateKey) - XCTAssertEqual(calcPublicKey, vector.publicKey) + XCTAssertEqual(wallet2.privateKey?.toHex(), vector.privateKey) + XCTAssertEqual(wallet2.address?.toHex(), vector.address) + XCTAssertEqual(wallet2.publicKey?.toHex(), vector.publicKey) } catch { print("\nCaught error: \(error)\n") From e4c7083830c77541a27ae7c1cd6188aa878b9c13 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 14:18:04 -0600 Subject: [PATCH 05/33] Formatting --- Sources/XyoClient/Address/Account.swift | 41 ++--- .../XyoClient/Address/AccountInstance.swift | 2 +- Sources/XyoClient/Address/XyoAddress.swift | 1 - .../Api/Archivist/ArchivistApiClient.swift | 11 +- .../XyoClient/BoundWitness/BoundWitness.swift | 11 +- .../BoundWitness/BoundWitnessBuilder.swift | 8 +- .../BoundWitness/Meta/BoundWitnessMeta.swift | 6 +- .../XyoClient/Module/ModuleQueryResult.swift | 5 +- Sources/XyoClient/Payload/Payload.swift | 12 +- .../XyoClient/Payload/PayloadBuilder.swift | 73 ++++---- Sources/XyoClient/Wallet/Bip39.swift | 29 +-- Sources/XyoClient/Wallet/Bip39Words.swift | 165 ++++++++++++------ Sources/XyoClient/Wallet/Extensions.swift | 4 +- Sources/XyoClient/Wallet/Hmac.swift | 4 +- Sources/XyoClient/Wallet/Secp256k1.swift | 6 +- .../Witness/Location/LocationWitness.swift | 3 +- .../SystemInfoDevicePayloadStruct.swift | 8 +- Tests/XyoClientTests/BoundWitness.swift | 5 +- .../TestData/TestPayload1.swift | 5 +- .../TestData/TestPayload2.swift | 3 +- Tests/XyoClientTests/Wallet.swift | 73 ++++---- Tests/XyoClientTests/WalletVectors.swift | 8 +- .../Generic/LocationPayloadTests.swift | 17 +- 23 files changed, 303 insertions(+), 197 deletions(-) diff --git a/Sources/XyoClient/Address/Account.swift b/Sources/XyoClient/Address/Account.swift index e1d1540..704166c 100644 --- a/Sources/XyoClient/Address/Account.swift +++ b/Sources/XyoClient/Address/Account.swift @@ -2,7 +2,7 @@ import Foundation import secp256k1 public func dataFromHex(_ hex: String) -> Data? { - let hex = hex.replacingOccurrences(of: " ", with: "") // Remove any spaces + let hex = hex.replacingOccurrences(of: " ", with: "") // Remove any spaces let len = hex.count // Ensure even length (hex must be in pairs) @@ -21,9 +21,9 @@ public func dataFromHex(_ hex: String) -> Data? { return data } -public extension Data { - init?(_ hex: String) { - let hex = hex.replacingOccurrences(of: " ", with: "") // Remove any spaces +extension Data { + public init?(_ hex: String) { + let hex = hex.replacingOccurrences(of: " ", with: "") // Remove any spaces let len = hex.count // Ensure even length (hex must be in pairs) @@ -38,7 +38,7 @@ public extension Data { data.append(byte) index = nextIndex } - + guard let result = dataFromHex(hex) else { return nil } self = result @@ -53,17 +53,17 @@ enum AccountError: Error { public class Account: AccountInstance, AccountStatic { private var _privateKey: Data? - + public var publicKey: Data? { - guard let privateKey = self.privateKey else {return nil} + guard let privateKey = self.privateKey else { return nil } return try? Account.privateKeyObjectFromKey(privateKey).publicKey.dataRepresentation } - + public static func fromPrivateKey(_ key: String) throws -> AccountInstance { guard let data = Data(key) else { throw AccountError.invalidPrivateKey } return Account(data) } - + public func sign(hash: String) throws -> Signature { guard let message = hash.hexToData() else { throw AccountError.invalidMessage } return try self.sign(message) @@ -71,7 +71,7 @@ public class Account: AccountInstance, AccountStatic { public func sign(_ hash: Hash) throws -> Signature { let context = try secp256k1.Context.create() - guard let privateKey = self.privateKey else {throw AccountError.invalidPrivateKey} + guard let privateKey = self.privateKey else { throw AccountError.invalidPrivateKey } defer { secp256k1_context_destroy(context) } @@ -100,30 +100,31 @@ public class Account: AccountInstance, AccountStatic { count: MemoryLayout.size(ofValue: signature2.data) ) - let result = try secp256k1.Signing.ECDSASignature(dataRepresentation: rawRepresentation).dataRepresentation + let result = try secp256k1.Signing.ECDSASignature(dataRepresentation: rawRepresentation) + .dataRepresentation try self.storePreviousHash(hash) return result } - + public func verify(_ msg: Data, _ signature: Signature) -> Bool { return false } - + public var keccakBytes: Data? { return publicKey?.keccak256() } - + public var address: Data? { - guard let keccakBytes = keccakBytes else {return nil} + guard let keccakBytes = keccakBytes else { return nil } return keccakBytes.subdata(in: 12.. any AccountInstance { return Account(key) } - + public var privateKey: Data? { return _privateKey } @@ -135,11 +136,11 @@ public class Account: AccountInstance, AccountStatic { init(_ privateKey: Data) { self._privateKey = privateKey } - + public var previousHash: Hash? { return try? retreivePreviousHash() } - + public static func privateKeyObjectFromKey(_ key: Data) throws -> secp256k1.Signing.PrivateKey { return try secp256k1.Signing.PrivateKey( dataRepresentation: key, format: .uncompressed) @@ -151,7 +152,7 @@ public class Account: AccountInstance, AccountStatic { Account.previousHashStore.setItem(address: address, previousHash: previousHash) } } - + internal func retreivePreviousHash() throws -> Hash? { guard let address = self.address else { throw AccountError.invalidAddress } return Account.previousHashStore.getItem(address: address) diff --git a/Sources/XyoClient/Address/AccountInstance.swift b/Sources/XyoClient/Address/AccountInstance.swift index 7097239..d817815 100644 --- a/Sources/XyoClient/Address/AccountInstance.swift +++ b/Sources/XyoClient/Address/AccountInstance.swift @@ -15,5 +15,5 @@ public protocol AccountInstance: PrivateKeyInstance { var previousHash: Hash? { get } var privateKey: Data? { get } var publicKey: Data? { get } - + } diff --git a/Sources/XyoClient/Address/XyoAddress.swift b/Sources/XyoClient/Address/XyoAddress.swift index 2a24250..c12e2f9 100644 --- a/Sources/XyoClient/Address/XyoAddress.swift +++ b/Sources/XyoClient/Address/XyoAddress.swift @@ -106,4 +106,3 @@ public class XyoAddress { } } } - diff --git a/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift b/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift index ac99de8..98adce9 100644 --- a/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift +++ b/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift @@ -9,7 +9,8 @@ public class XyoArchivistApiClient { ProcessInfo.processInfo.environment["XYO_API_MODULE"] ?? "Archivist" private static let ArchivistInsertQuerySchema = "network.xyo.query.archivist.insert" - private static let ArchivistInsertQuery: EncodablePayloadInstance = EncodablePayloadInstance(ArchivistInsertQuerySchema) + private static let ArchivistInsertQuery: EncodablePayloadInstance = EncodablePayloadInstance( + ArchivistInsertQuerySchema) let config: XyoArchivistApiConfig let queryAccount: AccountInstance @@ -66,7 +67,9 @@ public class XyoArchivistApiClient { ) // Check if the response data matches the expected result - if decodedResponse.data?.bw.typedPayload.payload_hashes.count == payloads.count { + if decodedResponse.data?.bw.typedPayload.payload_hashes.count + == payloads.count + { // Return the payloads array in case of success completion(payloads, nil) } else { @@ -91,7 +94,9 @@ public class XyoArchivistApiClient { } @available(iOS 15, *) - public func insert(payloads: [EncodablePayloadInstance]) async throws -> [EncodablePayloadInstance] { + public func insert(payloads: [EncodablePayloadInstance]) async throws + -> [EncodablePayloadInstance] + { // Build QueryBoundWitness let (bw, signed) = try BoundWitnessBuilder() .payloads(payloads) diff --git a/Sources/XyoClient/BoundWitness/BoundWitness.swift b/Sources/XyoClient/BoundWitness/BoundWitness.swift index 25a31bf..f6cdd54 100644 --- a/Sources/XyoClient/BoundWitness/BoundWitness.swift +++ b/Sources/XyoClient/BoundWitness/BoundWitness.swift @@ -13,8 +13,7 @@ public protocol EncodableBoundWitness: EncodablePayload, BoundWitnessFields, Enc public protocol BoundWitness: EncodableBoundWitness, EncodablePayload, Payload, Codable {} -public class BoundWitnessInstance: PayloadInstance -{ +public class BoundWitnessInstance: PayloadInstance { public var signatures: [String]? = nil public var addresses: [String] = [] @@ -62,6 +61,10 @@ public class BoundWitnessInstance: PayloadInstance } } -public typealias EncodableBoundWitnessWithMeta = EncodableWithCustomMetaInstance +public typealias EncodableBoundWitnessWithMeta = EncodableWithCustomMetaInstance< + BoundWitnessInstance, BoundWitnessMeta +> -public typealias BoundWitnessWithMeta = WithCustomMetaInstance +public typealias BoundWitnessWithMeta = WithCustomMetaInstance< + BoundWitnessInstance, BoundWitnessMeta +> diff --git a/Sources/XyoClient/BoundWitness/BoundWitnessBuilder.swift b/Sources/XyoClient/BoundWitness/BoundWitnessBuilder.swift index 6c1e09c..48a8a3d 100644 --- a/Sources/XyoClient/BoundWitness/BoundWitnessBuilder.swift +++ b/Sources/XyoClient/BoundWitness/BoundWitnessBuilder.swift @@ -30,7 +30,9 @@ public class BoundWitnessBuilder { return self } - public func payload(_ schema: String, _ payload: T) throws -> BoundWitnessBuilder { + public func payload(_ schema: String, _ payload: T) throws + -> BoundWitnessBuilder + { _payloads.append(payload) _payload_hashes.append(try PayloadBuilder.dataHash(from: payload)) _payload_schemas.append(schema) @@ -60,14 +62,14 @@ public class BoundWitnessBuilder { public func build() throws -> (EncodableBoundWitnessWithMeta, [EncodablePayloadInstance]) { let bw = BoundWitnessInstance() bw.addresses = _accounts.map { account in account.address!.toHex() } - bw.previous_hashes = _previous_hashes.map { hash in hash?.toHex()} + bw.previous_hashes = _previous_hashes.map { hash in hash?.toHex() } bw.payload_hashes = _payload_hashes.map { hash in hash.toHex() } bw.payload_schemas = _payload_schemas if _query != nil { bw.query = _query?.toHex() } let dataHash = try PayloadBuilder.dataHash(from: bw) - let signatures = try self.sign(hash: dataHash).map {signature in signature.toHex()} + let signatures = try self.sign(hash: dataHash).map { signature in signature.toHex() } let meta = BoundWitnessMeta(signatures) let bwWithMeta = EncodableWithCustomMetaInstance(from: bw, meta: meta) return (bwWithMeta, _payloads) diff --git a/Sources/XyoClient/BoundWitness/Meta/BoundWitnessMeta.swift b/Sources/XyoClient/BoundWitness/Meta/BoundWitnessMeta.swift index 144ce9f..e948e5a 100644 --- a/Sources/XyoClient/BoundWitness/Meta/BoundWitnessMeta.swift +++ b/Sources/XyoClient/BoundWitness/Meta/BoundWitnessMeta.swift @@ -8,17 +8,17 @@ public protocol BoundWitnessMetaProtocol: Decodable { public class BoundWitnessMeta: BoundWitnessMetaProtocol, Decodable, Encodable { public var client: String? public var signatures: [String]? - + enum CodingKeys: String, CodingKey { case client case signatures } - + public init(_ signatures: [String] = []) { self.client = "ios" self.signatures = signatures } - + public required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) client = try values.decode(String.self, forKey: .client) diff --git a/Sources/XyoClient/Module/ModuleQueryResult.swift b/Sources/XyoClient/Module/ModuleQueryResult.swift index 41dc2c9..78c9b22 100644 --- a/Sources/XyoClient/Module/ModuleQueryResult.swift +++ b/Sources/XyoClient/Module/ModuleQueryResult.swift @@ -4,7 +4,10 @@ public class ModuleQueryResult: Codable { public let bw: EncodableBoundWitnessWithMeta public let payloads: [EncodablePayloadInstance] public let errors: [EncodablePayloadInstance] - init(bw: EncodableBoundWitnessWithMeta, payloads: [EncodablePayloadInstance] = [], errors: [EncodablePayloadInstance] = []) { + init( + bw: EncodableBoundWitnessWithMeta, payloads: [EncodablePayloadInstance] = [], + errors: [EncodablePayloadInstance] = [] + ) { self.bw = bw self.payloads = payloads self.errors = errors diff --git a/Sources/XyoClient/Payload/Payload.swift b/Sources/XyoClient/Payload/Payload.swift index d374feb..220ebc8 100644 --- a/Sources/XyoClient/Payload/Payload.swift +++ b/Sources/XyoClient/Payload/Payload.swift @@ -1,6 +1,6 @@ import Foundation -public protocol PayloadFields : Encodable { +public protocol PayloadFields: Encodable { var schema: String { get } } @@ -12,24 +12,24 @@ open class EncodablePayloadInstance: EncodablePayload { public init(_ schema: String) { self.schema = schema.lowercased() } - + enum CodingKeys: String, CodingKey { case schema } - + public var schema: String - + public func toJson() throws -> String { return try PayloadBuilder.toJson(from: self) } } open class PayloadInstance: EncodablePayloadInstance, Payload { - + override public init(_ schema: String) { super.init(schema) } - + public required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let schema = try values.decode(String.self, forKey: .schema) diff --git a/Sources/XyoClient/Payload/PayloadBuilder.swift b/Sources/XyoClient/Payload/PayloadBuilder.swift index 7ee1843..ca6e64b 100644 --- a/Sources/XyoClient/Payload/PayloadBuilder.swift +++ b/Sources/XyoClient/Payload/PayloadBuilder.swift @@ -12,23 +12,23 @@ public protocol EncodableWithMeta: Encodable { public struct AnyEncodableWithMeta: EncodableWithMeta { private let _item: T - + public init(_ from: T) { self._item = from } - + public var payload: EncodablePayload { return self._item.payload } - + public var meta: Encodable? { return self._item.meta } - + public func toJson() throws -> String { return try self._item.toJson() } - + public func encode(to: Encoder) throws { return try self._item.encode(to: to) } @@ -44,7 +44,10 @@ public class EncodableEmptyMeta: Encodable {} public class EmptyMeta: Codable {} -public class EncodableWithMetaInstance: EncodableWithCustomMetaInstance { +public class EncodableWithMetaInstance: EncodableWithCustomMetaInstance< + T, EncodableEmptyMeta +> +{ public init(from: T) { super.init(from: from, meta: nil) } @@ -53,19 +56,19 @@ public class EncodableWithMetaInstance: EncodableWithCustom public class EncodableWithCustomMetaInstance: EncodableWithMeta { var _meta: M? = nil var _payload: T - + public var payload: EncodablePayload { return self._payload } - + public var typedPayload: T { return self._payload } - + public var meta: Encodable? { return self._meta } - + public var typedMeta: M? { return self._meta } @@ -74,11 +77,11 @@ public class EncodableWithCustomMetaInstance: case _hash = "$hash" case _meta = "$meta" } - + public var schema: String { return _payload.schema } - + public init(from: T, meta: M?) { _payload = from _meta = meta @@ -88,23 +91,25 @@ public class EncodableWithCustomMetaInstance: var container = encoder.container(keyedBy: CodingKeys.self) let hash = try PayloadBuilder.dataHash(from: _payload).toHex() try container.encode(hash, forKey: ._hash) - if (_meta != nil) { + if _meta != nil { try container.encode(_meta, forKey: ._meta) } try self._payload.encode(to: encoder) } - + public func toJson() throws -> String { return try PayloadBuilder.toJson(from: self) } } -public class WithCustomMetaInstance: EncodableWithCustomMetaInstance, Decodable { - +public class WithCustomMetaInstance: + EncodableWithCustomMetaInstance, Decodable +{ + override public init(from: T, meta: M?) { super.init(from: from, meta: meta) } - + public required init(from decoder: Decoder) throws { super.init(from: try T(from: decoder), meta: try M(from: decoder)) } @@ -114,7 +119,7 @@ public class WithMetaInstance: WithCustomMetaInstance Bool { // Remove keys starting with "_" return isHashableField(key) @@ -152,7 +157,7 @@ public class PayloadBuilder { return jsonObject } } - + private static func hashableFields(_ jsonObject: Any) -> Any { if let dictionary = jsonObject as? [String: Any] { // Process dictionaries: filter keys, sort, and recurse @@ -171,43 +176,51 @@ public class PayloadBuilder { return jsonObject } } - + static public func dataHash(from: T) throws -> Hash { let jsonString = try PayloadBuilder.toJson(from: from) return try jsonString.sha256() } - + static public func hash(fromWithMeta: T) throws -> Hash { let jsonString = try fromWithMeta.toJson() return try jsonString.sha256() } - - static public func hash(from: T, meta: M) throws -> Hash { + + static public func hash(from: T, meta: M) + throws -> Hash + { let withMeta = EncodableWithCustomMetaInstance(from: from, meta: meta) let jsonString = try withMeta.toJson() return try jsonString.sha256() } - + static public func hash(from: T) throws -> Hash { let withMeta = EncodableWithMetaInstance(from: from) let jsonString = try withMeta.toJson() return try jsonString.sha256() } - + static public func toJson(from: T) throws -> String { let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys let data = try encoder.encode(from) - guard let result = String(data: data, encoding: .utf8) else { throw PayloadBuilderError.encodingError } + guard let result = String(data: data, encoding: .utf8) else { + throw PayloadBuilderError.encodingError + } return result } - - static public func toJsonWithMeta(from: T, meta: M?) throws -> String { + + static public func toJsonWithMeta(from: T, meta: M?) + throws -> String + { let target = EncodableWithCustomMetaInstance(from: from, meta: meta) let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys let data = try encoder.encode(target) - guard let result = String(data: data, encoding: .utf8) else { throw PayloadBuilderError.encodingError } + guard let result = String(data: data, encoding: .utf8) else { + throw PayloadBuilderError.encodingError + } return result } } diff --git a/Sources/XyoClient/Wallet/Bip39.swift b/Sources/XyoClient/Wallet/Bip39.swift index 02b574a..54337d5 100644 --- a/Sources/XyoClient/Wallet/Bip39.swift +++ b/Sources/XyoClient/Wallet/Bip39.swift @@ -1,7 +1,7 @@ -import Foundation +import BigInt import CryptoKit import CryptoSwift -import BigInt +import Foundation public enum Bip39Error: Error { case invalidSeedLength @@ -27,12 +27,12 @@ let CHAINCODE_SIZE = 32 public class Bip39 { static let wordList: [String] = Bip39Words - + static func mnemonicToSeed(phrase: String) throws -> Data { let entropy = try mnemonicToEntropy(phrase: phrase) return try entropyToSeed(entropy: entropy) } - + static func generateEntropy(bits: Int) -> Data { precondition(bits % 32 == 0, "Entropy must be a multiple of 32") let byteCount = bits / 8 @@ -42,7 +42,7 @@ public class Bip39 { } return entropy } - + static func mnemonicToEntropy(phrase: String) throws -> Data { let words = phrase.lowercased().split(separator: " ").map(String.init) @@ -58,7 +58,8 @@ public class Bip39 { } // Step 3: Reconstruct combined bits from indices - let combinedBits = indices + let combinedBits = + indices .map { String($0, radix: 2).leftPad(toLength: 11, with: "0") } .joined() @@ -80,7 +81,7 @@ public class Bip39 { return entropy } - + static func entropyToMnemonic(entropy: Data) throws -> String { // Step 1: Validate entropy length guard [16, 20, 24, 28, 32].contains(entropy.count) else { @@ -92,7 +93,8 @@ public class Bip39 { let checksum = calculateChecksum(entropy: entropy, bits: checksumBits) // Step 3: Combine entropy and checksum into a binary string - let entropyBits = entropy.map { String($0, radix: 2).leftPad(toLength: 8, with: "0") }.joined() + let entropyBits = entropy.map { String($0, radix: 2).leftPad(toLength: 8, with: "0") } + .joined() let combinedBits = entropyBits + checksum // Step 4: Split combined bits into 11-bit chunks @@ -117,14 +119,15 @@ public class Bip39 { return mnemonicWords.joined(separator: " ") } - + static func entropyToSeed(entropy: Data, passphrase: String = "") throws -> Data { // Step 1: Convert entropy to mnemonic let mnemonic = try entropyToMnemonic(entropy: entropy) // Step 2: Apply PBKDF2 to derive the seed let salt = "mnemonic" + passphrase guard let mnemonicData = mnemonic.data(using: .utf8), - let saltData = salt.data(using: .utf8) else { + let saltData = salt.data(using: .utf8) + else { throw Bip39Error.failedToEncode } @@ -142,9 +145,9 @@ public class Bip39 { throw Bip39Error.failedToGenerateSeed } } - + static func rootPrivateKeyFromSeed(seed: Data) throws -> Key { - + let hmac = Hmac.hmacSha512(key: BITCOIN_SEED, data: seed) let privateKey = hmac.prefix(PRIVATE_KEY_SIZE) let chainCode = hmac.suffix(from: PRIVATE_KEY_SIZE) @@ -156,7 +159,7 @@ public class Bip39 { return Key(privateKey: privateKey, chainCode: chainCode) } - + private static func calculateChecksum(entropy: Data, bits: Int) -> String { let hash = Data(SHA256.hash(data: entropy)) let hashBits = hash.toBinaryString() diff --git a/Sources/XyoClient/Wallet/Bip39Words.swift b/Sources/XyoClient/Wallet/Bip39Words.swift index cb81497..022e334 100644 --- a/Sources/XyoClient/Wallet/Bip39Words.swift +++ b/Sources/XyoClient/Wallet/Bip39Words.swift @@ -1,16 +1,21 @@ public let Bip39Words: [String] = [ - "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", - "access", "accident", "account", "accuse", "achieve", "acid", "acoustic", "acquire", "across", "act", + "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", + "abuse", + "access", "accident", "account", "accuse", "achieve", "acid", "acoustic", "acquire", "across", + "act", "action", "actor", "actress", "actual", "adapt", "add", "addict", "address", "adjust", "admit", "adult", "advance", "advice", "aerobic", "affair", "afford", "afraid", "again", "age", "agent", "agree", "ahead", "aim", "air", "airport", "aisle", "alarm", "album", "alcohol", "alert", "alien", "all", "alley", "allow", "almost", "alone", "alpha", "already", "also", "alter", - "always", "amateur", "amazing", "among", "amount", "amused", "analyst", "anchor", "ancient", "anger", - "angle", "angry", "animal", "ankle", "announce", "annual", "another", "answer", "antenna", "antique", + "always", "amateur", "amazing", "among", "amount", "amused", "analyst", "anchor", "ancient", + "anger", + "angle", "angry", "animal", "ankle", "announce", "annual", "another", "answer", "antenna", + "antique", "anxiety", "any", "apart", "apology", "appear", "apple", "approve", "april", "arch", "arctic", "area", "arena", "argue", "arm", "armed", "armor", "army", "around", "arrange", "arrest", "arrive", "arrow", "art", "artefact", "artist", "artwork", "ask", "aspect", "assault", "asset", - "assist", "assume", "asthma", "athlete", "atom", "attack", "attend", "attitude", "attract", "auction", + "assist", "assume", "asthma", "athlete", "atom", "attack", "attend", "attitude", "attract", + "auction", "audit", "august", "aunt", "author", "auto", "autumn", "average", "avocado", "avoid", "awake", "aware", "away", "awesome", "awful", "awkward", "axis", "baby", "bachelor", "bacon", "badge", "bag", "balance", "balcony", "ball", "bamboo", "banana", "banner", "bar", "barely", "bargain", @@ -21,52 +26,73 @@ public let Bip39Words: [String] = [ "blind", "blood", "blossom", "blouse", "blue", "blur", "blush", "board", "boat", "body", "boil", "bomb", "bone", "bonus", "book", "boost", "border", "boring", "borrow", "boss", "bottom", "bounce", "box", "boy", "bracket", "brain", "brand", "brass", "brave", "bread", - "breeze", "brick", "bridge", "brief", "bright", "bring", "brisk", "broccoli", "broken", "bronze", + "breeze", "brick", "bridge", "brief", "bright", "bring", "brisk", "broccoli", "broken", + "bronze", "broom", "brother", "brown", "brush", "bubble", "buddy", "budget", "buffalo", "build", "bulb", "bulk", "bullet", "bundle", "bunker", "burden", "burger", "burst", "bus", "business", "busy", "butter", "buyer", "buzz", "cabbage", "cabin", "cable", "cactus", "cage", "cake", "call", "calm", "camera", "camp", "can", "canal", "cancel", "candy", "cannon", "canoe", "canvas", "canyon", "capable", "capital", "captain", "car", "carbon", "card", "cargo", "carpet", "carry", "cart", "case", "cash", "casino", "castle", "casual", "cat", "catalog", "catch", "category", - "cattle", "caught", "cause", "caution", "cave", "ceiling", "celery", "cement", "census", "century", - "cereal", "certain", "chair", "chalk", "champion", "change", "chaos", "chapter", "charge", "chase", + "cattle", "caught", "cause", "caution", "cave", "ceiling", "celery", "cement", "census", + "century", + "cereal", "certain", "chair", "chalk", "champion", "change", "chaos", "chapter", "charge", + "chase", "chat", "cheap", "check", "cheese", "chef", "cherry", "chest", "chicken", "chief", "child", - "chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn", "cigar", "cinnamon", "circle", + "chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn", "cigar", "cinnamon", + "circle", "citizen", "city", "civil", "claim", "clap", "clarify", "claw", "clay", "clean", "clerk", "clever", "click", "client", "cliff", "climb", "clinic", "clip", "clock", "clog", "close", "cloth", "cloud", "clown", "club", "clump", "cluster", "clutch", "coach", "coast", "coconut", "code", "coffee", "coil", "coin", "collect", "color", "column", "combine", "come", "comfort", - "comic", "common", "company", "concert", "conduct", "confirm", "congress", "connect", "consider", "control", + "comic", "common", "company", "concert", "conduct", "confirm", "congress", "connect", + "consider", "control", "convince", "cook", "cool", "copper", "copy", "coral", "core", "corn", "correct", "cost", - "cotton", "couch", "country", "couple", "course", "cousin", "cover", "coyote", "crack", "cradle", + "cotton", "couch", "country", "couple", "course", "cousin", "cover", "coyote", "crack", + "cradle", "craft", "cram", "crane", "crash", "crater", "crawl", "crazy", "cream", "credit", "creek", "crew", "cricket", "crime", "crisp", "critic", "crop", "cross", "crouch", "crowd", "crucial", "cruel", "cruise", "crumble", "crunch", "crush", "cry", "crystal", "cube", "culture", "cup", - "cupboard", "curious", "current", "curtain", "curve", "cushion", "custom", "cute", "cycle", "dad", + "cupboard", "curious", "current", "curtain", "curve", "cushion", "custom", "cute", "cycle", + "dad", "damage", "damp", "dance", "danger", "daring", "dash", "daughter", "dawn", "day", "deal", - "debate", "debris", "decade", "december", "decide", "decline", "decorate", "decrease", "deer", "defense", + "debate", "debris", "decade", "december", "decide", "decline", "decorate", "decrease", "deer", + "defense", "define", "defy", "degree", "delay", "deliver", "demand", "demise", "denial", "dentist", "deny", - "depart", "depend", "deposit", "depth", "deputy", "derive", "describe", "desert", "design", "desk", - "despair", "destroy", "detail", "detect", "develop", "device", "devote", "diagram", "dial", "diamond", - "diary", "dice", "diesel", "diet", "differ", "digital", "dignity", "dilemma", "dinner", "dinosaur", - "direct", "dirt", "disagree", "discover", "disease", "dish", "dismiss", "disorder", "display", "distance", - "divert", "divide", "divorce", "dizzy", "doctor", "document", "dog", "doll", "dolphin", "domain", + "depart", "depend", "deposit", "depth", "deputy", "derive", "describe", "desert", "design", + "desk", + "despair", "destroy", "detail", "detect", "develop", "device", "devote", "diagram", "dial", + "diamond", + "diary", "dice", "diesel", "diet", "differ", "digital", "dignity", "dilemma", "dinner", + "dinosaur", + "direct", "dirt", "disagree", "discover", "disease", "dish", "dismiss", "disorder", "display", + "distance", + "divert", "divide", "divorce", "dizzy", "doctor", "document", "dog", "doll", "dolphin", + "domain", "donate", "donkey", "donor", "door", "dose", "double", "dove", "draft", "dragon", "drama", "drastic", "draw", "dream", "dress", "drift", "drill", "drink", "drip", "drive", "drop", "drum", "dry", "duck", "dumb", "dune", "during", "dust", "dutch", "duty", "dwarf", "dynamic", "eager", "eagle", "early", "earn", "earth", "easily", "east", "easy", "echo", "ecology", "economy", "edge", "edit", "educate", "effort", "egg", "eight", "either", "elbow", - "elder", "electric", "elegant", "element", "elephant", "elevator", "elite", "else", "embark", "embody", - "embrace", "emerge", "emotion", "employ", "empower", "empty", "enable", "enact", "end", "endless", - "endorse", "enemy", "energy", "enforce", "engage", "engine", "enhance", "enjoy", "enlist", "enough", - "enrich", "enroll", "ensure", "enter", "entire", "entry", "envelope", "episode", "equal", "equip", + "elder", "electric", "elegant", "element", "elephant", "elevator", "elite", "else", "embark", + "embody", + "embrace", "emerge", "emotion", "employ", "empower", "empty", "enable", "enact", "end", + "endless", + "endorse", "enemy", "energy", "enforce", "engage", "engine", "enhance", "enjoy", "enlist", + "enough", + "enrich", "enroll", "ensure", "enter", "entire", "entry", "envelope", "episode", "equal", + "equip", "era", "erase", "erode", "erosion", "error", "erupt", "escape", "essay", "essence", "estate", - "eternal", "ethics", "evidence", "evil", "evoke", "evolve", "exact", "example", "excess", "exchange", - "excite", "exclude", "excuse", "execute", "exercise", "exhaust", "exhibit", "exile", "exist", "exit", - "exotic", "expand", "expect", "expire", "explain", "expose", "express", "extend", "extra", "eye", + "eternal", "ethics", "evidence", "evil", "evoke", "evolve", "exact", "example", "excess", + "exchange", + "excite", "exclude", "excuse", "execute", "exercise", "exhaust", "exhibit", "exile", "exist", + "exit", + "exotic", "expand", "expect", "expire", "explain", "expose", "express", "extend", "extra", + "eye", "eyebrow", "fabric", "face", "faculty", "fade", "faint", "faith", "fall", "false", "fame", "family", "famous", "fan", "fancy", "fantasy", "farm", "fashion", "fat", "fatal", "father", - "fatigue", "fault", "favorite", "feature", "february", "federal", "fee", "feed", "feel", "female", + "fatigue", "fault", "favorite", "feature", "february", "federal", "fee", "feed", "feel", + "female", "fence", "festival", "fetch", "fever", "few", "fiber", "fiction", "field", "figure", "file", "film", "filter", "final", "find", "fine", "finger", "finish", "fire", "firm", "first", "fiscal", "fish", "fit", "fitness", "fix", "flag", "flame", "flash", "flat", "flavor", @@ -89,11 +115,16 @@ public let Bip39Words: [String] = [ "holiday", "hollow", "home", "honey", "hood", "hope", "horn", "horror", "horse", "hospital", "host", "hotel", "hour", "hover", "hub", "huge", "human", "humble", "humor", "hundred", "hungry", "hunt", "hurdle", "hurry", "hurt", "husband", "hybrid", "ice", "icon", "idea", - "identify", "idle", "ignore", "ill", "illegal", "illness", "image", "imitate", "immense", "immune", - "impact", "impose", "improve", "impulse", "inch", "include", "income", "increase", "index", "indicate", - "indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit", "initial", "inject", "injury", - "inmate", "inner", "innocent", "input", "inquiry", "insane", "insect", "inside", "inspire", "install", - "intact", "interest", "into", "invest", "invite", "involve", "iron", "island", "isolate", "issue", + "identify", "idle", "ignore", "ill", "illegal", "illness", "image", "imitate", "immense", + "immune", + "impact", "impose", "improve", "impulse", "inch", "include", "income", "increase", "index", + "indicate", + "indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit", "initial", "inject", + "injury", + "inmate", "inner", "innocent", "input", "inquiry", "insane", "insect", "inside", "inspire", + "install", + "intact", "interest", "into", "invest", "invite", "involve", "iron", "island", "isolate", + "issue", "item", "ivory", "jacket", "jaguar", "jar", "jazz", "jealous", "jeans", "jelly", "jewel", "job", "join", "joke", "journey", "joy", "judge", "juice", "jump", "jungle", "junior", "junk", "just", "kangaroo", "keen", "keep", "ketchup", "key", "kick", "kid", "kidney", @@ -107,14 +138,17 @@ public let Bip39Words: [String] = [ "local", "lock", "logic", "lonely", "long", "loop", "lottery", "loud", "lounge", "love", "loyal", "lucky", "luggage", "lumber", "lunar", "lunch", "luxury", "lyrics", "machine", "mad", "magic", "magnet", "maid", "mail", "main", "major", "make", "mammal", "man", "manage", - "mandate", "mango", "mansion", "manual", "maple", "marble", "march", "margin", "marine", "market", - "marriage", "mask", "mass", "master", "match", "material", "math", "matrix", "matter", "maximum", + "mandate", "mango", "mansion", "manual", "maple", "marble", "march", "margin", "marine", + "market", + "marriage", "mask", "mass", "master", "match", "material", "math", "matrix", "matter", + "maximum", "maze", "meadow", "mean", "measure", "meat", "mechanic", "medal", "media", "melody", "melt", "member", "memory", "mention", "menu", "mercy", "merge", "merit", "merry", "mesh", "message", "metal", "method", "middle", "midnight", "milk", "million", "mimic", "mind", "minimum", "minor", "minute", "miracle", "mirror", "misery", "miss", "mistake", "mix", "mixed", "mixture", "mobile", "model", "modify", "mom", "moment", "monitor", "monkey", "monster", "month", "moon", "moral", - "more", "morning", "mosquito", "mother", "motion", "motor", "mountain", "mouse", "move", "movie", + "more", "morning", "mosquito", "mother", "motion", "motor", "mountain", "mouse", "move", + "movie", "much", "muffin", "mule", "multiply", "muscle", "museum", "mushroom", "music", "must", "mutual", "myself", "mystery", "myth", "naive", "name", "napkin", "narrow", "nasty", "nation", "nature", "near", "neck", "need", "negative", "neglect", "neither", "nephew", "nerve", "nest", "net", @@ -124,7 +158,8 @@ public let Bip39Words: [String] = [ "obtain", "obvious", "occur", "ocean", "october", "odor", "off", "offer", "office", "often", "oil", "okay", "old", "olive", "olympic", "omit", "once", "one", "onion", "online", "only", "open", "opera", "opinion", "oppose", "option", "orange", "orbit", "orchard", "order", - "ordinary", "organ", "orient", "original", "orphan", "ostrich", "other", "outdoor", "outer", "output", + "ordinary", "organ", "orient", "original", "orphan", "ostrich", "other", "outdoor", "outer", + "output", "outside", "oval", "oven", "over", "own", "owner", "oxygen", "oyster", "ozone", "pact", "paddle", "page", "pair", "palace", "palm", "panda", "panel", "panic", "panther", "paper", "parade", "parent", "park", "parrot", "party", "pass", "patch", "path", "patient", "patrol", @@ -134,28 +169,39 @@ public let Bip39Words: [String] = [ "pink", "pioneer", "pipe", "pistol", "pitch", "pizza", "place", "planet", "plastic", "plate", "play", "please", "pledge", "pluck", "plug", "plunge", "poem", "poet", "point", "polar", "pole", "police", "pond", "pony", "pool", "popular", "portion", "position", "possible", "post", - "potato", "pottery", "poverty", "powder", "power", "practice", "praise", "predict", "prefer", "prepare", - "present", "pretty", "prevent", "price", "pride", "primary", "print", "priority", "prison", "private", - "prize", "problem", "process", "produce", "profit", "program", "project", "promote", "proof", "property", - "prosper", "protect", "proud", "provide", "public", "pudding", "pull", "pulp", "pulse", "pumpkin", + "potato", "pottery", "poverty", "powder", "power", "practice", "praise", "predict", "prefer", + "prepare", + "present", "pretty", "prevent", "price", "pride", "primary", "print", "priority", "prison", + "private", + "prize", "problem", "process", "produce", "profit", "program", "project", "promote", "proof", + "property", + "prosper", "protect", "proud", "provide", "public", "pudding", "pull", "pulp", "pulse", + "pumpkin", "punch", "pupil", "puppy", "purchase", "purity", "purpose", "purse", "push", "put", "puzzle", - "pyramid", "quality", "quantum", "quarter", "question", "quick", "quit", "quiz", "quote", "rabbit", + "pyramid", "quality", "quantum", "quarter", "question", "quick", "quit", "quiz", "quote", + "rabbit", "raccoon", "race", "rack", "radar", "radio", "rail", "rain", "raise", "rally", "ramp", "ranch", "random", "range", "rapid", "rare", "rate", "rather", "raven", "raw", "razor", - "ready", "real", "reason", "rebel", "rebuild", "recall", "receive", "recipe", "record", "recycle", - "reduce", "reflect", "reform", "refuse", "region", "regret", "regular", "reject", "relax", "release", + "ready", "real", "reason", "rebel", "rebuild", "recall", "receive", "recipe", "record", + "recycle", + "reduce", "reflect", "reform", "refuse", "region", "regret", "regular", "reject", "relax", + "release", "relief", "rely", "remain", "remember", "remind", "remove", "render", "renew", "rent", "reopen", - "repair", "repeat", "replace", "report", "require", "rescue", "resemble", "resist", "resource", "response", - "result", "retire", "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm", "rib", + "repair", "repeat", "replace", "report", "require", "rescue", "resemble", "resist", "resource", + "response", + "result", "retire", "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm", + "rib", "ribbon", "rice", "rich", "ride", "ridge", "rifle", "right", "rigid", "ring", "riot", "ripple", "risk", "ritual", "rival", "river", "road", "roast", "robot", "robust", "rocket", "romance", "roof", "rookie", "room", "rose", "rotate", "rough", "round", "route", "royal", "rubber", "rude", "rug", "rule", "run", "runway", "rural", "sad", "saddle", "sadness", "safe", "sail", "salad", "salmon", "salon", "salt", "salute", "same", "sample", "sand", "satisfy", "satoshi", "sauce", "sausage", "save", "say", "scale", "scan", "scare", "scatter", - "scene", "scheme", "school", "science", "scissors", "scorpion", "scout", "scrap", "screen", "script", + "scene", "scheme", "school", "science", "scissors", "scorpion", "scout", "scrap", "screen", + "script", "scrub", "sea", "search", "season", "seat", "second", "secret", "section", "security", "seed", - "seek", "segment", "select", "sell", "seminar", "senior", "sense", "sentence", "series", "service", + "seek", "segment", "select", "sell", "seminar", "senior", "sense", "sentence", "series", + "service", "session", "settle", "setup", "seven", "shadow", "shaft", "shallow", "share", "shed", "shell", "sheriff", "shield", "shift", "shine", "ship", "shiver", "shock", "shoe", "shoot", "shop", "short", "shoulder", "shove", "shrimp", "shrug", "shuffle", "shy", "sibling", "sick", "side", @@ -168,12 +214,15 @@ public let Bip39Words: [String] = [ "sort", "soul", "sound", "soup", "source", "south", "space", "spare", "spatial", "spawn", "speak", "special", "speed", "spell", "spend", "sphere", "spice", "spider", "spike", "spin", "spirit", "split", "spoil", "sponsor", "spoon", "sport", "spot", "spray", "spread", "spring", - "spy", "square", "squeeze", "squirrel", "stable", "stadium", "staff", "stage", "stairs", "stamp", + "spy", "square", "squeeze", "squirrel", "stable", "stadium", "staff", "stage", "stairs", + "stamp", "stand", "start", "state", "stay", "steak", "steel", "stem", "step", "stereo", "stick", "still", "sting", "stock", "stomach", "stone", "stool", "story", "stove", "strategy", "street", - "strike", "strong", "struggle", "student", "stuff", "stumble", "style", "subject", "submit", "subway", + "strike", "strong", "struggle", "student", "stuff", "stumble", "style", "subject", "submit", + "subway", "success", "such", "sudden", "suffer", "sugar", "suggest", "suit", "summer", "sun", "sunny", - "sunset", "super", "supply", "supreme", "sure", "surface", "surge", "surprise", "surround", "survey", + "sunset", "super", "supply", "supreme", "sure", "surface", "surge", "surprise", "surround", + "survey", "suspect", "sustain", "swallow", "swamp", "swap", "swarm", "swear", "sweet", "swift", "swim", "swing", "switch", "sword", "symbol", "symptom", "syrup", "system", "table", "tackle", "tag", "tail", "talent", "talk", "tank", "tape", "target", "task", "taste", "tattoo", "taxi", @@ -188,12 +237,16 @@ public let Bip39Words: [String] = [ "trigger", "trim", "trip", "trophy", "trouble", "truck", "true", "truly", "trumpet", "trust", "truth", "try", "tube", "tuition", "tumble", "tuna", "tunnel", "turkey", "turn", "turtle", "twelve", "twenty", "twice", "twin", "twist", "two", "type", "typical", "ugly", "umbrella", - "unable", "unaware", "uncle", "uncover", "under", "undo", "unfair", "unfold", "unhappy", "uniform", - "unique", "unit", "universe", "unknown", "unlock", "until", "unusual", "unveil", "update", "upgrade", + "unable", "unaware", "uncle", "uncover", "under", "undo", "unfair", "unfold", "unhappy", + "uniform", + "unique", "unit", "universe", "unknown", "unlock", "until", "unusual", "unveil", "update", + "upgrade", "uphold", "upon", "upper", "upset", "urban", "urge", "usage", "use", "used", "useful", "useless", "usual", "utility", "vacant", "vacuum", "vague", "valid", "valley", "valve", "van", - "vanish", "vapor", "various", "vast", "vault", "vehicle", "velvet", "vendor", "venture", "venue", - "verb", "verify", "version", "very", "vessel", "veteran", "viable", "vibrant", "vicious", "victory", + "vanish", "vapor", "various", "vast", "vault", "vehicle", "velvet", "vendor", "venture", + "venue", + "verb", "verify", "version", "very", "vessel", "veteran", "viable", "vibrant", "vicious", + "victory", "video", "view", "village", "vintage", "violin", "virtual", "virus", "visa", "visit", "visual", "vital", "vivid", "vocal", "voice", "void", "volcano", "volume", "vote", "voyage", "wage", "wagon", "wait", "walk", "wall", "walnut", "want", "warfare", "warm", "warrior", "wash", @@ -202,5 +255,7 @@ public let Bip39Words: [String] = [ "wheel", "when", "where", "whip", "whisper", "wide", "width", "wife", "wild", "will", "win", "window", "wine", "wing", "wink", "winner", "winter", "wire", "wisdom", "wise", "wish", "witness", "wolf", "woman", "wonder", "wood", "wool", "word", "work", "world", - "worry", "worth", "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard", "year", "yellow", - "you", "young", "youth", "zebra", "zero", "zone", "zoo"] + "worry", "worth", "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard", "year", + "yellow", + "you", "young", "youth", "zebra", "zero", "zone", "zoo", +] diff --git a/Sources/XyoClient/Wallet/Extensions.swift b/Sources/XyoClient/Wallet/Extensions.swift index 73eaa07..ae894f9 100644 --- a/Sources/XyoClient/Wallet/Extensions.swift +++ b/Sources/XyoClient/Wallet/Extensions.swift @@ -56,7 +56,7 @@ extension BigInt { /// - Returns: A `Data` object representing the BigInt. func toData(length: Int? = nil) -> Data { var magnitudeBytes = self.magnitude.serialize() - + // Adjust for desired length, if specified if let length = length { if magnitudeBytes.count < length { @@ -68,7 +68,7 @@ extension BigInt { magnitudeBytes = magnitudeBytes.suffix(length) } } - + return Data(magnitudeBytes) } } diff --git a/Sources/XyoClient/Wallet/Hmac.swift b/Sources/XyoClient/Wallet/Hmac.swift index b7fba31..af49346 100644 --- a/Sources/XyoClient/Wallet/Hmac.swift +++ b/Sources/XyoClient/Wallet/Hmac.swift @@ -1,10 +1,10 @@ -import Foundation import CryptoKit +import Foundation struct Hmac { static func hmacSha512(key: Data, data: Data) -> Data { let symmetricKey = SymmetricKey(data: key) let hmac = HMAC.authenticationCode(for: data, using: symmetricKey) - return Data(hmac) // Convert the result to Data + return Data(hmac) // Convert the result to Data } } diff --git a/Sources/XyoClient/Wallet/Secp256k1.swift b/Sources/XyoClient/Wallet/Secp256k1.swift index 988db84..50cdc68 100644 --- a/Sources/XyoClient/Wallet/Secp256k1.swift +++ b/Sources/XyoClient/Wallet/Secp256k1.swift @@ -6,10 +6,12 @@ struct Point { } struct Secp256k1CurveConstants { - static let p = BigInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", radix: 16)! + static let p = BigInt( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", radix: 16)! static let a = BigInt(0) static let b = BigInt(7) - static let n = BigInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! + static let n = BigInt( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! static let g = Point( x: BigInt("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", radix: 16)!, y: BigInt("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", radix: 16)! diff --git a/Sources/XyoClient/Witness/Location/LocationWitness.swift b/Sources/XyoClient/Witness/Location/LocationWitness.swift index 8f3858d..42bc996 100644 --- a/Sources/XyoClient/Witness/Location/LocationWitness.swift +++ b/Sources/XyoClient/Witness/Location/LocationWitness.swift @@ -5,7 +5,8 @@ open class LocationWitness: WitnessModuleAsync { private var locationService: LocationService = LocationService() - override open func observe(completion: @escaping ([EncodablePayloadInstance]?, Error?) -> Void) { + override open func observe(completion: @escaping ([EncodablePayloadInstance]?, Error?) -> Void) + { locationService.requestAuthorization() locationService.requestLocation { result in DispatchQueue.main.async { diff --git a/Sources/XyoClient/Witness/SystemInfo/SystemInfoDevicePayloadStruct.swift b/Sources/XyoClient/Witness/SystemInfo/SystemInfoDevicePayloadStruct.swift index 1fa7eaa..6a3e8f7 100644 --- a/Sources/XyoClient/Witness/SystemInfo/SystemInfoDevicePayloadStruct.swift +++ b/Sources/XyoClient/Witness/SystemInfo/SystemInfoDevicePayloadStruct.swift @@ -6,11 +6,11 @@ class SystemInfoDevicePayloadStruct: Encodable, Decodable { var release: String? var sysname: String? var version: String? - + init() { - + } - + public required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) self.model = try values.decode(String.self, forKey: .model) @@ -27,7 +27,7 @@ class SystemInfoDevicePayloadStruct: Encodable, Decodable { case sysname case version } - + static func load() -> SystemInfoDevicePayloadStruct { var result = SystemInfoDevicePayloadStruct() var systemInfo = utsname() diff --git a/Tests/XyoClientTests/BoundWitness.swift b/Tests/XyoClientTests/BoundWitness.swift index 729d068..843585a 100644 --- a/Tests/XyoClientTests/BoundWitness.swift +++ b/Tests/XyoClientTests/BoundWitness.swift @@ -24,7 +24,8 @@ final class BoundWitnessTests: XCTestCase { let (bwJson, _) = try bw.build() let hash = try PayloadBuilder.hash(fromWithMeta: bwJson) let dataHash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) - XCTAssertEqual(dataHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") + XCTAssertEqual( + dataHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") XCTAssertEqual( hash.toHex(), "c267291c8169e428aaedbbf52792f9378ee03910401ef882b653a75f85370722") } @@ -37,7 +38,7 @@ final class BoundWitnessTests: XCTestCase { let (bwJson, _) = try bw.build() let bwJsonHash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) let e = try? bwJson.toJson() - if (e != nil) { + if e != nil { print("\n") print(e!) print("\n") diff --git a/Tests/XyoClientTests/TestData/TestPayload1.swift b/Tests/XyoClientTests/TestData/TestPayload1.swift index 6f7b7cc..772e899 100644 --- a/Tests/XyoClientTests/TestData/TestPayload1.swift +++ b/Tests/XyoClientTests/TestData/TestPayload1.swift @@ -1,5 +1,5 @@ -import XyoClient import Foundation +import XyoClient public class TestPayload1SubObject: Encodable { var number_value = 2 @@ -31,4 +31,5 @@ public class TestPayload1: EncodablePayloadInstance { } let testPayload1 = TestPayload1("network.xyo.test") -let testPayload1Hash: Hash = Hash("c915c56dd93b5e0db509d1a63ca540cfb211e11f03039b05e19712267bb8b6db")! +let testPayload1Hash: Hash = Hash( + "c915c56dd93b5e0db509d1a63ca540cfb211e11f03039b05e19712267bb8b6db")! diff --git a/Tests/XyoClientTests/TestData/TestPayload2.swift b/Tests/XyoClientTests/TestData/TestPayload2.swift index c65c1d8..843b513 100644 --- a/Tests/XyoClientTests/TestData/TestPayload2.swift +++ b/Tests/XyoClientTests/TestData/TestPayload2.swift @@ -32,4 +32,5 @@ public class TestPayload2: EncodablePayloadInstance { } let testPayload2 = TestPayload2("network.xyo.test") -let testPayload2Hash: Hash = Hash("c915c56dd93b5e0db509d1a63ca540cfb211e11f03039b05e19712267bb8b6db")! +let testPayload2Hash: Hash = Hash( + "c915c56dd93b5e0db509d1a63ca540cfb211e11f03039b05e19712267bb8b6db")! diff --git a/Tests/XyoClientTests/Wallet.swift b/Tests/XyoClientTests/Wallet.swift index 2f01480..6c40d0b 100644 --- a/Tests/XyoClientTests/Wallet.swift +++ b/Tests/XyoClientTests/Wallet.swift @@ -8,54 +8,67 @@ struct MasterKeyTestVector: Decodable { } class WalletTests: XCTestCase { - + let publicMasterKeyTestData: [MasterKeyTestVector] = - [ - MasterKeyTestVector( - hexEncodedSeed: "000102030405060708090a0b0c0d0e0f", - base58CheckEncodedKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" - ), - MasterKeyTestVector( - hexEncodedSeed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", - base58CheckEncodedKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" - ), - MasterKeyTestVector( - hexEncodedSeed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", - base58CheckEncodedKey: "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6" - ), - MasterKeyTestVector( - hexEncodedSeed: "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678", - base58CheckEncodedKey: "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv" - ) - ] + [ + MasterKeyTestVector( + hexEncodedSeed: "000102030405060708090a0b0c0d0e0f", + base58CheckEncodedKey: + "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + ), + MasterKeyTestVector( + hexEncodedSeed: + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + base58CheckEncodedKey: + "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" + ), + MasterKeyTestVector( + hexEncodedSeed: + "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + base58CheckEncodedKey: + "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6" + ), + MasterKeyTestVector( + hexEncodedSeed: "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678", + base58CheckEncodedKey: + "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv" + ), + ] func test_generateFromMnemonic() { - let phrase = "later puppy sound rebuild rebuild noise ozone amazing hope broccoli crystal grief" + let phrase = + "later puppy sound rebuild rebuild noise ozone amazing hope broccoli crystal grief" let rootEntropy = "7d55c33f59ab352ba7a03e6d638cd533" - let paths = ["0/4", "44'/0'/0'", "44'/60'/0'/0/0", "44'/60'/0'/0/1", "49'/0'/0'", "84'/0'/0'", "84'/0'/0'/0"] - let pathAddresses = ["0/4", "44'/0'/0'", "44'/60'/0'/0/0", "44'/60'/0'/0/1", "49'/0'/0'", "84'/0'/0'", "84'/0'/0'/0"] - + let paths = [ + "0/4", "44'/0'/0'", "44'/60'/0'/0/0", "44'/60'/0'/0/1", "49'/0'/0'", "84'/0'/0'", + "84'/0'/0'/0", + ] + let pathAddresses = [ + "0/4", "44'/0'/0'", "44'/60'/0'/0/0", "44'/60'/0'/0/1", "49'/0'/0'", "84'/0'/0'", + "84'/0'/0'/0", + ] + do { - + let entropy = try Bip39.mnemonicToEntropy(phrase: phrase) let reconstructedMnemonic = try Bip39.entropyToMnemonic(entropy: entropy) print("Original Mnemonic: \(phrase)") print("Reconstructed Mnemonic: \(reconstructedMnemonic)") XCTAssertEqual(phrase, reconstructedMnemonic) - + let sut = try Wallet(phrase: phrase, path: "m/44'/60'/0'/0/0") - let sut2 = try Wallet(phrase: phrase, path:"m/44'/60'/0'/0/1") + let sut2 = try Wallet(phrase: phrase, path: "m/44'/60'/0'/0/1") XCTAssertNotEqual(sut.address, sut2.address) let calcedEntropy = try Bip39.mnemonicToEntropy(phrase: phrase) let calcedPhrase = try Bip39.entropyToMnemonic(entropy: calcedEntropy) - + XCTAssertEqual(phrase, calcedPhrase) - + XCTAssertEqual(rootEntropy, calcedEntropy.toHex()) - + //let sutPath0 = try sut.derivePath(path: paths[0]) //XCTAssertEqual(sutPath0.address?.toHex(), pathAddresses[0]) - + // Assert XCTAssertNil(nil) } catch { diff --git a/Tests/XyoClientTests/WalletVectors.swift b/Tests/XyoClientTests/WalletVectors.swift index eada50c..c943c04 100644 --- a/Tests/XyoClientTests/WalletVectors.swift +++ b/Tests/XyoClientTests/WalletVectors.swift @@ -18,7 +18,7 @@ let testVectors: [TestVector] = [ path: "m/44'/60'/0'/0/0", address: "e46c258c74c7c1df33d7caa4c2c664dc0843ab3f", privateKey: "96a7705eedbb701a03ee235911253fd3eb80e48a06106c0bf957d42b72bd8efa", -// publicKey: "a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" + // publicKey: "a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" publicKey: "03a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206" ) ] @@ -31,9 +31,10 @@ class WalletVectorTests: XCTestCase { let wallet = try Wallet(phrase: vector.phrase, path: vector.path) XCTAssertEqual(wallet.privateKey?.toHex(), vector.privateKey) + let pubKey = wallet.publicKey?.toHex() XCTAssertEqual(wallet.address?.toHex(), vector.address) XCTAssertEqual(wallet.publicKey?.toHex(), vector.publicKey) - + let entropy = try Bip39.mnemonicToEntropy(phrase: vector.phrase) let seed = try Bip39.entropyToSeed(entropy: entropy) let pk = try Bip39.rootPrivateKeyFromSeed(seed: seed) @@ -42,8 +43,7 @@ class WalletVectorTests: XCTestCase { XCTAssertEqual(wallet2.privateKey?.toHex(), vector.privateKey) XCTAssertEqual(wallet2.address?.toHex(), vector.address) XCTAssertEqual(wallet2.publicKey?.toHex(), vector.publicKey) - } - catch { + } catch { print("\nCaught error: \(error)\n") XCTAssertTrue(false) } diff --git a/Tests/XyoClientTests/Witness/Location/Generic/LocationPayloadTests.swift b/Tests/XyoClientTests/Witness/Location/Generic/LocationPayloadTests.swift index 0e87f12..78f54a6 100644 --- a/Tests/XyoClientTests/Witness/Location/Generic/LocationPayloadTests.swift +++ b/Tests/XyoClientTests/Witness/Location/Generic/LocationPayloadTests.swift @@ -67,14 +67,15 @@ class LocationPayloadTests: XCTestCase { print("\n dataHash") print(dataHash.toHex()) print("\n") - XCTAssertEqual(dataHash, Data("0c1f0c80481b0f391a677eab542a594a192081325b6416acc3dc99db23355ee2")) - + XCTAssertEqual( + dataHash, Data("0c1f0c80481b0f391a677eab542a594a192081325b6416acc3dc99db23355ee2")) + let payloadWithMeta = EncodableWithMetaInstance(from: payload) - + let encoderMeta = JSONEncoder() encoderMeta.outputFormatting = [.sortedKeys, .prettyPrinted] // Consistent output for tests let jsonWithMetaData = try encoder.encode(payloadWithMeta) - + let jsonWithMetaString = String(data: jsonWithMetaData, encoding: .utf8)! let jsonWithMetaString2 = try payloadWithMeta.toJson() print("\n jsonWithMetaString") @@ -82,12 +83,13 @@ class LocationPayloadTests: XCTestCase { print("\n jsonWithMetaString2") print(jsonWithMetaString2) print("\n") - + let hash = try PayloadBuilder.hash(fromWithMeta: payloadWithMeta) print("\n hash") print(hash.toHex()) print("\n") - XCTAssertEqual(hash, Data("5a4bb96eb1af7840321cb8a3503ab944957c06111869cc0746e985f49061e746")) + XCTAssertEqual( + hash, Data("5a4bb96eb1af7840321cb8a3503ab944957c06111869cc0746e985f49061e746")) } func testLocationPayloadEncodingHandlesNilValues() throws { @@ -124,6 +126,7 @@ class LocationPayloadTests: XCTestCase { """ XCTAssertEqual(jsonString, expectedJSON) let hash = try PayloadBuilder.dataHash(from: payload) - XCTAssertEqual(hash, Data("c1bd7396f998a50d20401efd4b5da0cf6670f9418c6f60b42f4c54f3663305c3")) + XCTAssertEqual( + hash, Data("c1bd7396f998a50d20401efd4b5da0cf6670f9418c6f60b42f4c54f3663305c3")) } } From f6c6e80f5b2e62d144def9ed1b304edf98a4cce2 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 14:18:12 -0600 Subject: [PATCH 06/33] Simplify --- Sources/XyoClient/Wallet/Wallet.swift | 82 ++++++++++++--------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index d6cc14b..ea3db8d 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -1,7 +1,7 @@ -import Foundation +import BigInt import CryptoKit import CryptoSwift -import BigInt +import Foundation import secp256k1 public struct Key { @@ -24,39 +24,39 @@ public enum WalletError: Error { } public class Wallet: Account, WalletInstance { - + static let defaultPath = "m/44'/60'/0'/0/0" - + // Define the secp256k1 curve order - static let secp256k1CurveOrder = BigInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! + static let secp256k1CurveOrder = BigInt( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! - public func derivePath(path: String) throws -> any WalletInstance { let key = try Wallet.deriveKey(from: self._key, path: path) return try Wallet(key: key) } - + private var _key: Key init(key: Key) throws { self._key = key super.init(key.privateKey) } - + convenience init(phrase: String, path: String = defaultPath) throws { let seed = try Bip39.mnemonicToSeed(phrase: phrase) try self.init(seed: seed, path: path) } - + convenience init(seed: Data, path: String = defaultPath) throws { let rootKey = try Bip39.rootPrivateKeyFromSeed(seed: seed) let derivedKey = try Wallet.deriveKey(from: rootKey, path: path) try self.init(key: derivedKey) } - + static func deriveKey(from parentKey: Key, path: String) throws -> Key { let components = path.split(separator: "/") - + guard components.first == "m" else { throw WalletError.invalidPath } @@ -69,7 +69,7 @@ public class Wallet: Account, WalletInstance { throw WalletError.invalidPathComponent } - let derivedIndex = hardened ? index | 0x80000000 : index + let derivedIndex = hardened ? index | 0x8000_0000 : index currentKey = try deriveChildKey(parentKey: currentKey, index: derivedIndex) } @@ -80,7 +80,7 @@ public class Wallet: Account, WalletInstance { private static func deriveChildKey(parentKey: Key, index: UInt32) throws -> Key { var data = Data() - if index >= 0x80000000 { + if index >= 0x8000_0000 { // Hardened key: prepend 0x00 and parent private key guard parentKey.privateKey.count == 32 else { throw WalletError.invalidPrivateKeyLength @@ -90,7 +90,8 @@ public class Wallet: Account, WalletInstance { print("Hardened Derivation: Prepended Private Key") } else { // Normal key: use the compressed public key - guard let publicKey = try? getCompressedPublicKey(privateKey: parentKey.privateKey) else { + guard let publicKey = try? getCompressedPublicKey(privateKey: parentKey.privateKey) + else { throw WalletError.failedToGetPublicKey } data.append(publicKey) @@ -100,37 +101,24 @@ public class Wallet: Account, WalletInstance { // Append the index data.append(contentsOf: withUnsafeBytes(of: index.bigEndian, Array.init)) print("Data for HMAC: \(data.toHex())") - + // Perform HMAC-SHA512 guard data.count == 37 else { throw WalletError.failedToGenerateHmac } let hmac = Hmac.hmacSha512(key: parentKey.chainCode, data: data) - let derivedPrivateKeyBytes = hmac.prefix(32) // Left 32 bytes (L) - let derivedChainCode = hmac.suffix(32) // Right 32 bytes (R) - - print("HMAC Output: \(Data(hmac).toHex())") - print("Derived Private Key Bytes (L): \(derivedPrivateKeyBytes.toHex())") - print("Derived Chain Code (R): \(derivedChainCode.toHex())") + let derivedChainCode = hmac.suffix(32) // Right 32 bytes (R) // Convert L to an integer - let L = BigInt(derivedPrivateKeyBytes.toHex(), radix: 16)! -// let curveOrder = BigInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! - print("Curve Order: \(secp256k1CurveOrder)") - print("L as BigInt: \(L)") - + let L = BigInt(hmac.prefix(32).toHex(), radix: 16)! // Validate L guard L < secp256k1CurveOrder else { throw WalletError.invalidChildKey } // Compute the child private key: (L + parentPrivateKey) % curveOrder -// let parentPrivateKeyInt = BigInt(Data(parentKey.privateKey)) let parentPrivateKeyInt = BigInt(parentKey.privateKey.toHex(), radix: 16)! let childPrivateKeyInt = (L + parentPrivateKeyInt) % secp256k1CurveOrder - print("Parent Private Key as Hex: \(parentKey.privateKey.toHexString())") - print("Parent Private Key as BigInt: \(parentPrivateKeyInt)") - print("Child Private Key as BigInt: \(childPrivateKeyInt)") // Ensure the child private key is valid guard childPrivateKeyInt != 0 else { @@ -139,41 +127,45 @@ public class Wallet: Account, WalletInstance { // Convert the child private key back to Data var childPrivateKey = childPrivateKeyInt.toData() + guard childPrivateKey.count <= 32 else { + throw WalletError.invalidChildKey + } + if childPrivateKey.count < 32 { - // Pad with leading zeros to make it 32 bytes - let padding = Data(repeating: 0, count: 32 - data.count) - childPrivateKey = padding + data + // Pad with leading zeros to make it 32 bytes + let padding = Data(repeating: 0, count: 32 - data.count) + childPrivateKey = padding + data } - let test = childPrivateKey.toHexString() // Return the new child key return Key(privateKey: childPrivateKey, chainCode: Data(derivedChainCode)) } - + private static func getCompressedPublicKey(privateKey: Data) throws -> Data { - guard let uncompressedKey = XyoAddress(privateKey: privateKey.toHexString()).publicKeyBytes else { + guard let uncompressedKey = XyoAddress(privateKey: privateKey.toHexString()).publicKeyBytes + else { throw WalletError.failedToGetPublicKey } -// return publicKeyBytes + // return publicKeyBytes // Ensure the input key is exactly 64 bytes guard uncompressedKey.count == 64 else { throw WalletError.invalidPrivateKeyLength } - + // Extract x and y coordinates - let x = uncompressedKey.prefix(32) // First 32 bytes are x - let y = uncompressedKey.suffix(32) // Last 32 bytes are y - + let x = uncompressedKey.prefix(32) // First 32 bytes are x + let y = uncompressedKey.suffix(32) // Last 32 bytes are y + // Convert y to an integer to determine parity let yInt = BigInt(y.toHex(), radix: 16)! let isEven = yInt % 2 == 0 - + // Determine the prefix based on the parity of y let prefix: UInt8 = isEven ? 0x02 : 0x03 - + // Construct the compressed key: prefix + x - var compressedKey = Data([prefix]) // Start with the prefix - compressedKey.append(x) // Append the x-coordinate + var compressedKey = Data([prefix]) // Start with the prefix + compressedKey.append(x) // Append the x-coordinate return compressedKey } From 2fe4f99373d553d61230608073737625a5bb2e31 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 14:32:17 -0600 Subject: [PATCH 07/33] Correct address displaying --- Sources/XyoClient/Address/Account.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/XyoClient/Address/Account.swift b/Sources/XyoClient/Address/Account.swift index 704166c..05511d4 100644 --- a/Sources/XyoClient/Address/Account.swift +++ b/Sources/XyoClient/Address/Account.swift @@ -111,12 +111,19 @@ public class Account: AccountInstance, AccountStatic { } public var keccakBytes: Data? { - return publicKey?.keccak256() + + return publicKey? + // Drop the `0x04` from the beginning of the key + .dropFirst() + // Then take the keccak256 hash of the key + .keccak256() } public var address: Data? { + // Get the keccak hash of the public key guard let keccakBytes = keccakBytes else { return nil } - return keccakBytes.subdata(in: 12.. Date: Wed, 4 Dec 2024 14:45:53 -0600 Subject: [PATCH 08/33] Match Kotlin interface with compressed/uncompressed public key --- Sources/XyoClient/Address/Account.swift | 39 ++++++++++++++++++- .../XyoClient/Address/AccountInstance.swift | 1 + Tests/XyoClientTests/WalletVectors.swift | 1 - 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Sources/XyoClient/Address/Account.swift b/Sources/XyoClient/Address/Account.swift index 05511d4..117e26b 100644 --- a/Sources/XyoClient/Address/Account.swift +++ b/Sources/XyoClient/Address/Account.swift @@ -1,3 +1,4 @@ +import BigInt import Foundation import secp256k1 @@ -52,9 +53,19 @@ enum AccountError: Error { } public class Account: AccountInstance, AccountStatic { + private var _privateKey: Data? public var publicKey: Data? { + guard + let publicKeyUncompressed = publicKeyUncompressed? + // Drop the `0x04` from the beginning of the key + .dropFirst() + else { return nil } + return try? Account.getCompressedKeyFrom(uncompressedPublicKey: publicKeyUncompressed) + } + + public var publicKeyUncompressed: Data? { guard let privateKey = self.privateKey else { return nil } return try? Account.privateKeyObjectFromKey(privateKey).publicKey.dataRepresentation } @@ -112,7 +123,7 @@ public class Account: AccountInstance, AccountStatic { public var keccakBytes: Data? { - return publicKey? + return publicKeyUncompressed? // Drop the `0x04` from the beginning of the key .dropFirst() // Then take the keccak256 hash of the key @@ -120,7 +131,7 @@ public class Account: AccountInstance, AccountStatic { } public var address: Data? { - // Get the keccak hash of the public key + // Get the keccak hash ofthe public key guard let keccakBytes = keccakBytes else { return nil } // Return the last 20 bytes of the keccak hash return keccakBytes.suffix(20) @@ -164,4 +175,28 @@ public class Account: AccountInstance, AccountStatic { guard let address = self.address else { throw AccountError.invalidAddress } return Account.previousHashStore.getItem(address: address) } + + public static func getCompressedKeyFrom(uncompressedPublicKey: Data) throws -> Data { + // Ensure the input key is exactly 64 bytes + guard uncompressedPublicKey.count == 64 else { + throw WalletError.invalidPrivateKeyLength + } + + // Extract x and y coordinates + let x = uncompressedPublicKey.prefix(32) // First 32 bytes are x + let y = uncompressedPublicKey.suffix(32) // Last 32 bytes are y + + // Convert y to an integer to determine parity + let yInt = BigInt(y.toHex(), radix: 16)! + let isEven = yInt % 2 == 0 + + // Determine the prefix based on the parity of y + let prefix: UInt8 = isEven ? 0x02 : 0x03 + + // Construct the compressed key: prefix + x + var compressedKey = Data([prefix]) // Start with the prefix + compressedKey.append(x) // Append the x-coordinate + + return compressedKey + } } diff --git a/Sources/XyoClient/Address/AccountInstance.swift b/Sources/XyoClient/Address/AccountInstance.swift index d817815..1ac7e17 100644 --- a/Sources/XyoClient/Address/AccountInstance.swift +++ b/Sources/XyoClient/Address/AccountInstance.swift @@ -15,5 +15,6 @@ public protocol AccountInstance: PrivateKeyInstance { var previousHash: Hash? { get } var privateKey: Data? { get } var publicKey: Data? { get } + var publicKeyUncompressed: Data? { get } } diff --git a/Tests/XyoClientTests/WalletVectors.swift b/Tests/XyoClientTests/WalletVectors.swift index c943c04..bef2b5c 100644 --- a/Tests/XyoClientTests/WalletVectors.swift +++ b/Tests/XyoClientTests/WalletVectors.swift @@ -31,7 +31,6 @@ class WalletVectorTests: XCTestCase { let wallet = try Wallet(phrase: vector.phrase, path: vector.path) XCTAssertEqual(wallet.privateKey?.toHex(), vector.privateKey) - let pubKey = wallet.publicKey?.toHex() XCTAssertEqual(wallet.address?.toHex(), vector.address) XCTAssertEqual(wallet.publicKey?.toHex(), vector.publicKey) From 07ff90e88027df6c42226bc250abbbeb9bd60746 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 14:48:48 -0600 Subject: [PATCH 09/33] Add tests for uncompressed public keys --- Tests/XyoClientTests/WalletVectors.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Tests/XyoClientTests/WalletVectors.swift b/Tests/XyoClientTests/WalletVectors.swift index bef2b5c..285ab8e 100644 --- a/Tests/XyoClientTests/WalletVectors.swift +++ b/Tests/XyoClientTests/WalletVectors.swift @@ -3,23 +3,24 @@ import XCTest @testable import XyoClient struct TestVector: Decodable { - let phrase: String - let path: String let address: String + let path: String + let phrase: String let privateKey: String let publicKey: String + let publicKeyUncompressed: String } let phrase = "later puppy sound rebuild rebuild noise ozone amazing hope broccoli crystal grief" let testVectors: [TestVector] = [ .init( - phrase: phrase, - path: "m/44'/60'/0'/0/0", address: "e46c258c74c7c1df33d7caa4c2c664dc0843ab3f", + path: "m/44'/60'/0'/0/0", + phrase: phrase, privateKey: "96a7705eedbb701a03ee235911253fd3eb80e48a06106c0bf957d42b72bd8efa", - // publicKey: "a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" - publicKey: "03a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206" + publicKey: "03a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206", + publicKeyUncompressed: "04a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" ) ] @@ -33,6 +34,7 @@ class WalletVectorTests: XCTestCase { XCTAssertEqual(wallet.privateKey?.toHex(), vector.privateKey) XCTAssertEqual(wallet.address?.toHex(), vector.address) XCTAssertEqual(wallet.publicKey?.toHex(), vector.publicKey) + XCTAssertEqual(wallet.publicKeyUncompressed?.toHex(), vector.publicKeyUncompressed) let entropy = try Bip39.mnemonicToEntropy(phrase: vector.phrase) let seed = try Bip39.entropyToSeed(entropy: entropy) @@ -42,6 +44,7 @@ class WalletVectorTests: XCTestCase { XCTAssertEqual(wallet2.privateKey?.toHex(), vector.privateKey) XCTAssertEqual(wallet2.address?.toHex(), vector.address) XCTAssertEqual(wallet2.publicKey?.toHex(), vector.publicKey) + XCTAssertEqual(wallet2.publicKeyUncompressed?.toHex(), vector.publicKeyUncompressed) } catch { print("\nCaught error: \(error)\n") XCTAssertTrue(false) From bf37e8bd9a284dc9feae9a3ae78a94c571cc835e Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 14:55:21 -0600 Subject: [PATCH 10/33] Tests for creation via key & phrase --- Tests/XyoClientTests/WalletVectors.swift | 58 ++++++++++++++---------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/Tests/XyoClientTests/WalletVectors.swift b/Tests/XyoClientTests/WalletVectors.swift index 285ab8e..fb51e19 100644 --- a/Tests/XyoClientTests/WalletVectors.swift +++ b/Tests/XyoClientTests/WalletVectors.swift @@ -11,13 +11,11 @@ struct TestVector: Decodable { let publicKeyUncompressed: String } -let phrase = "later puppy sound rebuild rebuild noise ozone amazing hope broccoli crystal grief" - -let testVectors: [TestVector] = [ +let testCases: [TestVector] = [ .init( address: "e46c258c74c7c1df33d7caa4c2c664dc0843ab3f", path: "m/44'/60'/0'/0/0", - phrase: phrase, + phrase: "later puppy sound rebuild rebuild noise ozone amazing hope broccoli crystal grief", privateKey: "96a7705eedbb701a03ee235911253fd3eb80e48a06106c0bf957d42b72bd8efa", publicKey: "03a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206", publicKeyUncompressed: "04a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" @@ -26,28 +24,38 @@ let testVectors: [TestVector] = [ class WalletVectorTests: XCTestCase { - func test_vectors() { - do { - let vector = testVectors[0] - - let wallet = try Wallet(phrase: vector.phrase, path: vector.path) - XCTAssertEqual(wallet.privateKey?.toHex(), vector.privateKey) - XCTAssertEqual(wallet.address?.toHex(), vector.address) - XCTAssertEqual(wallet.publicKey?.toHex(), vector.publicKey) - XCTAssertEqual(wallet.publicKeyUncompressed?.toHex(), vector.publicKeyUncompressed) + func testCreationFromPrivateKey() { + for vector in testCases { + do { + let entropy = try Bip39.mnemonicToEntropy(phrase: vector.phrase) + let seed = try Bip39.entropyToSeed(entropy: entropy) + let pk = try Bip39.rootPrivateKeyFromSeed(seed: seed) + let pkDerived = try Wallet.deriveKey(from: pk, path: vector.path) + let wallet2 = try Wallet(key: pkDerived) + XCTAssertEqual(wallet2.privateKey?.toHex(), vector.privateKey) + XCTAssertEqual(wallet2.address?.toHex(), vector.address) + XCTAssertEqual(wallet2.publicKey?.toHex(), vector.publicKey) + XCTAssertEqual(wallet2.publicKeyUncompressed?.toHex(), vector.publicKeyUncompressed) + } catch { + print("\nCaught error: \(error)\n") + XCTAssertTrue(false) + } + } + } - let entropy = try Bip39.mnemonicToEntropy(phrase: vector.phrase) - let seed = try Bip39.entropyToSeed(entropy: entropy) - let pk = try Bip39.rootPrivateKeyFromSeed(seed: seed) - let pkDerived = try Wallet.deriveKey(from: pk, path: vector.path) - let wallet2 = try Wallet(key: pkDerived) - XCTAssertEqual(wallet2.privateKey?.toHex(), vector.privateKey) - XCTAssertEqual(wallet2.address?.toHex(), vector.address) - XCTAssertEqual(wallet2.publicKey?.toHex(), vector.publicKey) - XCTAssertEqual(wallet2.publicKeyUncompressed?.toHex(), vector.publicKeyUncompressed) - } catch { - print("\nCaught error: \(error)\n") - XCTAssertTrue(false) + func testCreationFromPhrase() { + for vector in testCases { + do { + let wallet = try Wallet(phrase: vector.phrase, path: vector.path) + XCTAssertEqual(wallet.privateKey?.toHex(), vector.privateKey) + XCTAssertEqual(wallet.address?.toHex(), vector.address) + XCTAssertEqual(wallet.publicKey?.toHex(), vector.publicKey) + XCTAssertEqual(wallet.publicKeyUncompressed?.toHex(), vector.publicKeyUncompressed) + } catch { + print("\nCaught error: \(error)\n") + XCTAssertTrue(false) + } } } + } From 359d7b57f9c1288c8fb221ab8ffe929ff59b9dcc Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 14:59:09 -0600 Subject: [PATCH 11/33] Formatting --- Tests/XyoClientTests/WalletVectors.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/XyoClientTests/WalletVectors.swift b/Tests/XyoClientTests/WalletVectors.swift index fb51e19..f6b2d9c 100644 --- a/Tests/XyoClientTests/WalletVectors.swift +++ b/Tests/XyoClientTests/WalletVectors.swift @@ -18,7 +18,8 @@ let testCases: [TestVector] = [ phrase: "later puppy sound rebuild rebuild noise ozone amazing hope broccoli crystal grief", privateKey: "96a7705eedbb701a03ee235911253fd3eb80e48a06106c0bf957d42b72bd8efa", publicKey: "03a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206", - publicKeyUncompressed: "04a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" + publicKeyUncompressed: + "04a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206f850a6decf0d0277c8ea237d865a06b6237f07eaf4273217ed6b2ed830161bef" ) ] @@ -57,5 +58,4 @@ class WalletVectorTests: XCTestCase { } } } - } From 2dd8723dd0670916e7bb805fbf3c3c199ddbe3c4 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 15:10:32 -0600 Subject: [PATCH 12/33] Formatting --- Sources/XyoClient/Address/Account.swift | 101 ++++++++++++------------ 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/Sources/XyoClient/Address/Account.swift b/Sources/XyoClient/Address/Account.swift index 117e26b..58d1ff7 100644 --- a/Sources/XyoClient/Address/Account.swift +++ b/Sources/XyoClient/Address/Account.swift @@ -48,14 +48,39 @@ extension Data { enum AccountError: Error { case invalidAddress - case invalidPrivateKey case invalidMessage + case invalidPrivateKey } public class Account: AccountInstance, AccountStatic { + public static var previousHashStore: PreviousHashStore = CoreDataPreviousHashStore() + private var _privateKey: Data? + public var address: Data? { + // Get the keccak hash of the public key + guard let keccakBytes = keccakBytes else { return nil } + // Return the last 20 bytes of the keccak hash + return keccakBytes.suffix(20) + } + + public var keccakBytes: Data? { + return publicKeyUncompressed? + // Drop the `0x04` from the beginning of the key + .dropFirst() + // Then take the keccak256 hash of the key + .keccak256() + } + + public var previousHash: Hash? { + return try? retreivePreviousHash() + } + + public var privateKey: Data? { + return _privateKey + } + public var publicKey: Data? { guard let publicKeyUncompressed = publicKeyUncompressed? @@ -70,14 +95,21 @@ public class Account: AccountInstance, AccountStatic { return try? Account.privateKeyObjectFromKey(privateKey).publicKey.dataRepresentation } + public static func fromPrivateKey(_ key: Data) -> any AccountInstance { + return Account(key) + } + public static func fromPrivateKey(_ key: String) throws -> AccountInstance { guard let data = Data(key) else { throw AccountError.invalidPrivateKey } return Account(data) } - public func sign(hash: String) throws -> Signature { - guard let message = hash.hexToData() else { throw AccountError.invalidMessage } - return try self.sign(message) + public static func random() -> AccountInstance { + return Account(generateRandomBytes()) + } + + init(_ privateKey: Data) { + self._privateKey = privateKey } public func sign(_ hash: Hash) throws -> Signature { @@ -117,51 +149,18 @@ public class Account: AccountInstance, AccountStatic { return result } - public func verify(_ msg: Data, _ signature: Signature) -> Bool { - return false - } - - public var keccakBytes: Data? { - - return publicKeyUncompressed? - // Drop the `0x04` from the beginning of the key - .dropFirst() - // Then take the keccak256 hash of the key - .keccak256() - } - - public var address: Data? { - // Get the keccak hash ofthe public key - guard let keccakBytes = keccakBytes else { return nil } - // Return the last 20 bytes of the keccak hash - return keccakBytes.suffix(20) - } - - public static var previousHashStore: PreviousHashStore = CoreDataPreviousHashStore() - - public static func fromPrivateKey(_ key: Data) -> any AccountInstance { - return Account(key) - } - - public var privateKey: Data? { - return _privateKey - } - - public static func random() -> AccountInstance { - return Account(generateRandomBytes()) - } - - init(_ privateKey: Data) { - self._privateKey = privateKey + public func sign(hash: String) throws -> Signature { + guard let message = hash.hexToData() else { throw AccountError.invalidMessage } + return try self.sign(message) } - public var previousHash: Hash? { - return try? retreivePreviousHash() + public func verify(_ msg: Data, _ signature: Signature) -> Bool { + return false } - public static func privateKeyObjectFromKey(_ key: Data) throws -> secp256k1.Signing.PrivateKey { - return try secp256k1.Signing.PrivateKey( - dataRepresentation: key, format: .uncompressed) + internal func retreivePreviousHash() throws -> Hash? { + guard let address = self.address else { throw AccountError.invalidAddress } + return Account.previousHashStore.getItem(address: address) } internal func storePreviousHash(_ newValue: Hash?) throws { @@ -171,15 +170,10 @@ public class Account: AccountInstance, AccountStatic { } } - internal func retreivePreviousHash() throws -> Hash? { - guard let address = self.address else { throw AccountError.invalidAddress } - return Account.previousHashStore.getItem(address: address) - } - public static func getCompressedKeyFrom(uncompressedPublicKey: Data) throws -> Data { // Ensure the input key is exactly 64 bytes guard uncompressedPublicKey.count == 64 else { - throw WalletError.invalidPrivateKeyLength + throw AccountError.invalidPrivateKey } // Extract x and y coordinates @@ -199,4 +193,9 @@ public class Account: AccountInstance, AccountStatic { return compressedKey } + + public static func privateKeyObjectFromKey(_ key: Data) throws -> secp256k1.Signing.PrivateKey { + return try secp256k1.Signing.PrivateKey( + dataRepresentation: key, format: .uncompressed) + } } From b5d0ec51ef25744bad1f20a76fe55ec6ecc42e67 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Wed, 4 Dec 2024 15:25:38 -0600 Subject: [PATCH 13/33] Spelling --- Sources/XyoClient/Address/Account.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/XyoClient/Address/Account.swift b/Sources/XyoClient/Address/Account.swift index 58d1ff7..591ce68 100644 --- a/Sources/XyoClient/Address/Account.swift +++ b/Sources/XyoClient/Address/Account.swift @@ -74,7 +74,7 @@ public class Account: AccountInstance, AccountStatic { } public var previousHash: Hash? { - return try? retreivePreviousHash() + return try? retrievePreviousHash() } public var privateKey: Data? { @@ -158,7 +158,7 @@ public class Account: AccountInstance, AccountStatic { return false } - internal func retreivePreviousHash() throws -> Hash? { + internal func retrievePreviousHash() throws -> Hash? { guard let address = self.address else { throw AccountError.invalidAddress } return Account.previousHashStore.getItem(address: address) } From c1fe4f5596ec12a379431c28572711b28817ed55 Mon Sep 17 00:00:00 2001 From: Arie Trouw Date: Wed, 4 Dec 2024 13:42:06 -0800 Subject: [PATCH 14/33] Round Trip Test --- Tests/XyoClientTests/BoundWitness.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/XyoClientTests/BoundWitness.swift b/Tests/XyoClientTests/BoundWitness.swift index 843585a..4852557 100644 --- a/Tests/XyoClientTests/BoundWitness.swift +++ b/Tests/XyoClientTests/BoundWitness.swift @@ -19,6 +19,8 @@ final class BoundWitnessTests: XCTestCase { let payloadDataHash = try PayloadBuilder.dataHash(from: testPayload1) XCTAssertEqual(payloadDataHash, testPayload1Hash) let address = Account.fromPrivateKey(testVectorPrivateKey.hexToData()!) + XCTAssertEqual(testVectorPrivateKey, address.privateKey!.toHex()) + XCTAssertEqual(testVectorAddress, address.address!.toHex()) let bw = try BoundWitnessBuilder().signer(address).payload( "network.xyo.test", TestPayload1("network.xyo.test")) let (bwJson, _) = try bw.build() From 72b22c9e54f9bbbc66be6399c7c9abdd2b7f59bb Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 05:12:42 -0600 Subject: [PATCH 15/33] Add Id payload --- Sources/XyoClient/Payload/Core/Id.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Sources/XyoClient/Payload/Core/Id.swift diff --git a/Sources/XyoClient/Payload/Core/Id.swift b/Sources/XyoClient/Payload/Core/Id.swift new file mode 100644 index 0000000..c43f95f --- /dev/null +++ b/Sources/XyoClient/Payload/Core/Id.swift @@ -0,0 +1,23 @@ +open class IdPayload: EncodablePayloadInstance { + + public static let schema: String = "network.xyo.id" + + var salt: String + + public init(salt: String) { + self.salt = salt + super.init(IdPayload.schema) + } + + enum CodingKeys: String, CodingKey { + case salt + case schema + } + + override open func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.schema, forKey: .schema) + try container.encode(self.salt, forKey: .salt) + } +} + From 3787f2899b148c73d179eb111d7f5868bcf1fb28 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 05:55:27 -0600 Subject: [PATCH 16/33] BW Sequence test cases --- .../TestData/TestBoundWitnessSequence.swift | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift diff --git a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift new file mode 100644 index 0000000..d4dc796 --- /dev/null +++ b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift @@ -0,0 +1,49 @@ +import XyoClient + +public struct BoundWitnessSequenceTestCase { + public var privateKeys: [String] + public var addresses: [Address] + public var payloads: [EncodablePayloadInstance] + public var payloadHashes: [String] + public var previousHashes: [String?] +} + +public struct PayloadsWithHashes { + public var payloads: [EncodablePayloadInstance] + public var payloadHashes: [String] +} + +let payloadSequences: [PayloadsWithHashes] = [ + .init(payloads: [IdPayload(0)], payloadHashes: [""]), + .init(payloads: [IdPayload(1)], payloadHashes: [""]), + .init(payloads: [IdPayload(2), IdPayload(3)], payloadHashes: ["", ""]), + +] + +let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( + privateKeys: [], + addresses: [], + payloads: payloadSequences[0].payloads, + payloadHashes: payloadSequences[0].payloadHashes, + previousHashes: [] +) + +let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( + privateKeys: [], + addresses: [], + payloads: payloadSequences[1].payloads, + payloadHashes: payloadSequences[1].payloadHashes, + previousHashes: [] +) + +let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( + privateKeys: [], + addresses: [], + payloads: payloadSequences[2].payloads, + payloadHashes: payloadSequences[2].payloadHashes, + previousHashes: [] +) + +let boundWitnessSequenceTestCases = [ + boundWitnessSequenceTestCase1, boundWitnessSequenceTestCase2, boundWitnessSequenceTestCase3, +] From 352afbb544a6f7a7959c62c008589895a31390ea Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 05:56:14 -0600 Subject: [PATCH 17/33] convenience init from UInt --- Sources/XyoClient/Payload/Core/Id.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/XyoClient/Payload/Core/Id.swift b/Sources/XyoClient/Payload/Core/Id.swift index c43f95f..28abbf7 100644 --- a/Sources/XyoClient/Payload/Core/Id.swift +++ b/Sources/XyoClient/Payload/Core/Id.swift @@ -4,11 +4,16 @@ open class IdPayload: EncodablePayloadInstance { var salt: String - public init(salt: String) { + public override init(_ salt: String) { self.salt = salt super.init(IdPayload.schema) } + public convenience init(_ salt: UInt) { + let s = "\(salt)" + self.init(s) + } + enum CodingKeys: String, CodingKey { case salt case schema @@ -20,4 +25,3 @@ open class IdPayload: EncodablePayloadInstance { try container.encode(self.salt, forKey: .salt) } } - From 20c4d592f547de7a388976755195349ff528a5bf Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 06:17:12 -0600 Subject: [PATCH 18/33] Add data hash to test cases --- .../TestData/TestBoundWitnessSequence.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift index d4dc796..d447b52 100644 --- a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift +++ b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift @@ -6,6 +6,7 @@ public struct BoundWitnessSequenceTestCase { public var payloads: [EncodablePayloadInstance] public var payloadHashes: [String] public var previousHashes: [String?] + public var dataHash: String } public struct PayloadsWithHashes { @@ -25,7 +26,8 @@ let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( addresses: [], payloads: payloadSequences[0].payloads, payloadHashes: payloadSequences[0].payloadHashes, - previousHashes: [] + previousHashes: [nil , nil], + dataHash: "" ) let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( @@ -33,7 +35,8 @@ let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( addresses: [], payloads: payloadSequences[1].payloads, payloadHashes: payloadSequences[1].payloadHashes, - previousHashes: [] + previousHashes: [], + dataHash: "" ) let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( @@ -41,7 +44,8 @@ let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( addresses: [], payloads: payloadSequences[2].payloads, payloadHashes: payloadSequences[2].payloadHashes, - previousHashes: [] + previousHashes: [], + dataHash: "" ) let boundWitnessSequenceTestCases = [ From f229b7e92d91bab69c64d7bc3b6f8976c0333641 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 07:41:31 -0600 Subject: [PATCH 19/33] Match src folder structure --- .../{ => Account}/Account.swift | 0 .../{ => Account}/AccountServices.swift | 0 .../{ => Account}/Address.swift | 0 .../{ => BoundWitness}/BoundWitness.swift | 24 +++++++++++++++++++ Tests/XyoClientTests/{ => Panel}/Panel.swift | 0 .../CoreDataPreviousHashStoreTests.swift | 0 .../XyoClientTests/{ => Wallet}/Wallet.swift | 0 .../{ => Wallet}/WalletVectors.swift | 0 8 files changed, 24 insertions(+) rename Tests/XyoClientTests/{ => Account}/Account.swift (100%) rename Tests/XyoClientTests/{ => Account}/AccountServices.swift (100%) rename Tests/XyoClientTests/{ => Account}/Address.swift (100%) rename Tests/XyoClientTests/{ => BoundWitness}/BoundWitness.swift (74%) rename Tests/XyoClientTests/{ => Panel}/Panel.swift (100%) rename Tests/XyoClientTests/{ => PrevioiusHashStore}/CoreDataPreviousHashStoreTests.swift (100%) rename Tests/XyoClientTests/{ => Wallet}/Wallet.swift (100%) rename Tests/XyoClientTests/{ => Wallet}/WalletVectors.swift (100%) diff --git a/Tests/XyoClientTests/Account.swift b/Tests/XyoClientTests/Account/Account.swift similarity index 100% rename from Tests/XyoClientTests/Account.swift rename to Tests/XyoClientTests/Account/Account.swift diff --git a/Tests/XyoClientTests/AccountServices.swift b/Tests/XyoClientTests/Account/AccountServices.swift similarity index 100% rename from Tests/XyoClientTests/AccountServices.swift rename to Tests/XyoClientTests/Account/AccountServices.swift diff --git a/Tests/XyoClientTests/Address.swift b/Tests/XyoClientTests/Account/Address.swift similarity index 100% rename from Tests/XyoClientTests/Address.swift rename to Tests/XyoClientTests/Account/Address.swift diff --git a/Tests/XyoClientTests/BoundWitness.swift b/Tests/XyoClientTests/BoundWitness/BoundWitness.swift similarity index 74% rename from Tests/XyoClientTests/BoundWitness.swift rename to Tests/XyoClientTests/BoundWitness/BoundWitness.swift index 4852557..273f685 100644 --- a/Tests/XyoClientTests/BoundWitness.swift +++ b/Tests/XyoClientTests/BoundWitness/BoundWitness.swift @@ -59,4 +59,28 @@ final class BoundWitnessTests: XCTestCase { XCTAssertEqual( bwJsonHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") } + + func testSequence_hash_returnsExpectedHash() throws { + for testCase in boundWitnessSequenceTestCases { + // Create accounts + let signers = testCase.addresses.map { Account.fromPrivateKey($0) } + // Ensure correct initial account state + for (i, previousHash) in testCase.previousHashes.enumerated() { + XCTAssertEqual(testCase.previousHashes[i], previousHash) + } + + // Build the BW + let bw = try BoundWitnessBuilder().signers(signers).payloads(testCase.payloads) + let (bwJson, _) = try bw.build() + let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) + + // Ensure the BW is correct + XCTAssertEqual(hash.toHex(), testCase.dataHash) + + // Ensure correct ending account state + for signer in signers { + XCTAssertEqual(signer.previousHash, hash) + } + } + } } diff --git a/Tests/XyoClientTests/Panel.swift b/Tests/XyoClientTests/Panel/Panel.swift similarity index 100% rename from Tests/XyoClientTests/Panel.swift rename to Tests/XyoClientTests/Panel/Panel.swift diff --git a/Tests/XyoClientTests/CoreDataPreviousHashStoreTests.swift b/Tests/XyoClientTests/PrevioiusHashStore/CoreDataPreviousHashStoreTests.swift similarity index 100% rename from Tests/XyoClientTests/CoreDataPreviousHashStoreTests.swift rename to Tests/XyoClientTests/PrevioiusHashStore/CoreDataPreviousHashStoreTests.swift diff --git a/Tests/XyoClientTests/Wallet.swift b/Tests/XyoClientTests/Wallet/Wallet.swift similarity index 100% rename from Tests/XyoClientTests/Wallet.swift rename to Tests/XyoClientTests/Wallet/Wallet.swift diff --git a/Tests/XyoClientTests/WalletVectors.swift b/Tests/XyoClientTests/Wallet/WalletVectors.swift similarity index 100% rename from Tests/XyoClientTests/WalletVectors.swift rename to Tests/XyoClientTests/Wallet/WalletVectors.swift From 551f570e08b3a3f6cb9c407dde7a4f3a748e2a0e Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 07:49:21 -0600 Subject: [PATCH 20/33] Modularize BIP-39 code --- Sources/XyoClient/Wallet/{ => BIP-39}/Bip39.swift | 0 Sources/XyoClient/Wallet/{ => BIP-39}/Bip39Words.swift | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Sources/XyoClient/Wallet/{ => BIP-39}/Bip39.swift (100%) rename Sources/XyoClient/Wallet/{ => BIP-39}/Bip39Words.swift (100%) diff --git a/Sources/XyoClient/Wallet/Bip39.swift b/Sources/XyoClient/Wallet/BIP-39/Bip39.swift similarity index 100% rename from Sources/XyoClient/Wallet/Bip39.swift rename to Sources/XyoClient/Wallet/BIP-39/Bip39.swift diff --git a/Sources/XyoClient/Wallet/Bip39Words.swift b/Sources/XyoClient/Wallet/BIP-39/Bip39Words.swift similarity index 100% rename from Sources/XyoClient/Wallet/Bip39Words.swift rename to Sources/XyoClient/Wallet/BIP-39/Bip39Words.swift From 4da3a8858d842f905b03e522c382cc214e532967 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 07:49:37 -0600 Subject: [PATCH 21/33] Single constant for secp256k1CurveOrder --- Sources/XyoClient/Wallet/Extensions.swift | 7 ------- Sources/XyoClient/Wallet/Wallet.swift | 8 ++------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Sources/XyoClient/Wallet/Extensions.swift b/Sources/XyoClient/Wallet/Extensions.swift index ae894f9..0c05b6a 100644 --- a/Sources/XyoClient/Wallet/Extensions.swift +++ b/Sources/XyoClient/Wallet/Extensions.swift @@ -29,13 +29,6 @@ extension String { } } -extension BigUInt { - static let secp256k1CurveOrder = BigUInt( - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", - radix: 16 - )! -} - extension Data { func toBigInt() -> BigInt { return BigInt(self) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index ea3db8d..9c4ab3b 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -27,10 +27,6 @@ public class Wallet: Account, WalletInstance { static let defaultPath = "m/44'/60'/0'/0/0" - // Define the secp256k1 curve order - static let secp256k1CurveOrder = BigInt( - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! - public func derivePath(path: String) throws -> any WalletInstance { let key = try Wallet.deriveKey(from: self._key, path: path) return try Wallet(key: key) @@ -112,13 +108,13 @@ public class Wallet: Account, WalletInstance { // Convert L to an integer let L = BigInt(hmac.prefix(32).toHex(), radix: 16)! // Validate L - guard L < secp256k1CurveOrder else { + guard L < Secp256k1CurveConstants.n else { throw WalletError.invalidChildKey } // Compute the child private key: (L + parentPrivateKey) % curveOrder let parentPrivateKeyInt = BigInt(parentKey.privateKey.toHex(), radix: 16)! - let childPrivateKeyInt = (L + parentPrivateKeyInt) % secp256k1CurveOrder + let childPrivateKeyInt = (L + parentPrivateKeyInt) % Secp256k1CurveConstants.n // Ensure the child private key is valid guard childPrivateKeyInt != 0 else { From 0617378b32b615b7fcf42522d35345afe113ff72 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 07:50:52 -0600 Subject: [PATCH 22/33] Remove print statements --- Sources/XyoClient/Wallet/Wallet.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index 9c4ab3b..91139e3 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -83,7 +83,6 @@ public class Wallet: Account, WalletInstance { } data.append(0x00) data.append(parentKey.privateKey) - print("Hardened Derivation: Prepended Private Key") } else { // Normal key: use the compressed public key guard let publicKey = try? getCompressedPublicKey(privateKey: parentKey.privateKey) @@ -91,12 +90,10 @@ public class Wallet: Account, WalletInstance { throw WalletError.failedToGetPublicKey } data.append(publicKey) - print("Non-Hardened Derivation: Appended Public Key") } // Append the index data.append(contentsOf: withUnsafeBytes(of: index.bigEndian, Array.init)) - print("Data for HMAC: \(data.toHex())") // Perform HMAC-SHA512 guard data.count == 37 else { From 9de9ff220369fac262e6015fe7de5db97bf068ba Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 07:57:34 -0600 Subject: [PATCH 23/33] Formatting --- Tests/XyoClientTests/BoundWitness/BoundWitness.swift | 8 ++++---- .../TestData/TestBoundWitnessSequence.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitness.swift b/Tests/XyoClientTests/BoundWitness/BoundWitness.swift index 273f685..86a5611 100644 --- a/Tests/XyoClientTests/BoundWitness/BoundWitness.swift +++ b/Tests/XyoClientTests/BoundWitness/BoundWitness.swift @@ -59,7 +59,7 @@ final class BoundWitnessTests: XCTestCase { XCTAssertEqual( bwJsonHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") } - + func testSequence_hash_returnsExpectedHash() throws { for testCase in boundWitnessSequenceTestCases { // Create accounts @@ -68,15 +68,15 @@ final class BoundWitnessTests: XCTestCase { for (i, previousHash) in testCase.previousHashes.enumerated() { XCTAssertEqual(testCase.previousHashes[i], previousHash) } - + // Build the BW let bw = try BoundWitnessBuilder().signers(signers).payloads(testCase.payloads) let (bwJson, _) = try bw.build() let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) - + // Ensure the BW is correct XCTAssertEqual(hash.toHex(), testCase.dataHash) - + // Ensure correct ending account state for signer in signers { XCTAssertEqual(signer.previousHash, hash) diff --git a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift index d447b52..6c66390 100644 --- a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift +++ b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift @@ -26,7 +26,7 @@ let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( addresses: [], payloads: payloadSequences[0].payloads, payloadHashes: payloadSequences[0].payloadHashes, - previousHashes: [nil , nil], + previousHashes: [nil, nil], dataHash: "" ) From eb4d3e2074ea0a8373ef311246612d1f87275044 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 07:57:46 -0600 Subject: [PATCH 24/33] Refactor to match standard variable naming convention --- Sources/XyoClient/Wallet/Wallet.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index 91139e3..cf45a65 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -100,7 +100,6 @@ public class Wallet: Account, WalletInstance { throw WalletError.failedToGenerateHmac } let hmac = Hmac.hmacSha512(key: parentKey.chainCode, data: data) - let derivedChainCode = hmac.suffix(32) // Right 32 bytes (R) // Convert L to an integer let L = BigInt(hmac.prefix(32).toHex(), radix: 16)! @@ -108,6 +107,7 @@ public class Wallet: Account, WalletInstance { guard L < Secp256k1CurveConstants.n else { throw WalletError.invalidChildKey } + let R = hmac.suffix(32) // Right 32 bytes (R) // Compute the child private key: (L + parentPrivateKey) % curveOrder let parentPrivateKeyInt = BigInt(parentKey.privateKey.toHex(), radix: 16)! @@ -131,7 +131,7 @@ public class Wallet: Account, WalletInstance { } // Return the new child key - return Key(privateKey: childPrivateKey, chainCode: Data(derivedChainCode)) + return Key(privateKey: childPrivateKey, chainCode: Data(R)) } private static func getCompressedPublicKey(privateKey: Data) throws -> Data { @@ -139,7 +139,7 @@ public class Wallet: Account, WalletInstance { else { throw WalletError.failedToGetPublicKey } - // return publicKeyBytes + // Ensure the input key is exactly 64 bytes guard uncompressedKey.count == 64 else { throw WalletError.invalidPrivateKeyLength From 74ccdf65bc31a6fc7c6f88613fe77c0a01b838a4 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 08:06:17 -0600 Subject: [PATCH 25/33] Single method for obtaining compressed public key --- Sources/XyoClient/Wallet/Wallet.swift | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index cf45a65..b0788ef 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -84,7 +84,7 @@ public class Wallet: Account, WalletInstance { data.append(0x00) data.append(parentKey.privateKey) } else { - // Normal key: use the compressed public key + // Append the compressed public key guard let publicKey = try? getCompressedPublicKey(privateKey: parentKey.privateKey) else { throw WalletError.failedToGetPublicKey @@ -135,31 +135,10 @@ public class Wallet: Account, WalletInstance { } private static func getCompressedPublicKey(privateKey: Data) throws -> Data { - guard let uncompressedKey = XyoAddress(privateKey: privateKey.toHexString()).publicKeyBytes + guard let uncompressedPublicKey = XyoAddress(privateKey: privateKey.toHexString()).publicKeyBytes else { throw WalletError.failedToGetPublicKey } - - // Ensure the input key is exactly 64 bytes - guard uncompressedKey.count == 64 else { - throw WalletError.invalidPrivateKeyLength - } - - // Extract x and y coordinates - let x = uncompressedKey.prefix(32) // First 32 bytes are x - let y = uncompressedKey.suffix(32) // Last 32 bytes are y - - // Convert y to an integer to determine parity - let yInt = BigInt(y.toHex(), radix: 16)! - let isEven = yInt % 2 == 0 - - // Determine the prefix based on the parity of y - let prefix: UInt8 = isEven ? 0x02 : 0x03 - - // Construct the compressed key: prefix + x - var compressedKey = Data([prefix]) // Start with the prefix - compressedKey.append(x) // Append the x-coordinate - - return compressedKey + return try Account.getCompressedKeyFrom(uncompressedPublicKey: uncompressedPublicKey) } } From a677c76f045f7ac5ecf517f61548bf0990a3f1f0 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 08:10:48 -0600 Subject: [PATCH 26/33] Overloaded method for compressed public key --- Sources/XyoClient/Address/Account.swift | 14 ++++++++++++-- Sources/XyoClient/Wallet/Wallet.swift | 12 +++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Sources/XyoClient/Address/Account.swift b/Sources/XyoClient/Address/Account.swift index 591ce68..f90559b 100644 --- a/Sources/XyoClient/Address/Account.swift +++ b/Sources/XyoClient/Address/Account.swift @@ -87,7 +87,7 @@ public class Account: AccountInstance, AccountStatic { // Drop the `0x04` from the beginning of the key .dropFirst() else { return nil } - return try? Account.getCompressedKeyFrom(uncompressedPublicKey: publicKeyUncompressed) + return try? Account.getCompressedPublicKeyFrom(uncompressedPublicKey: publicKeyUncompressed) } public var publicKeyUncompressed: Data? { @@ -170,7 +170,17 @@ public class Account: AccountInstance, AccountStatic { } } - public static func getCompressedKeyFrom(uncompressedPublicKey: Data) throws -> Data { + public static func getCompressedPublicKeyFrom(privateKey: Data) throws -> Data { + guard + let uncompressedPublicKey = XyoAddress(privateKey: privateKey.toHexString()) + .publicKeyBytes + else { + throw WalletError.failedToGetPublicKey + } + return try Account.getCompressedPublicKeyFrom(uncompressedPublicKey: uncompressedPublicKey) + } + + public static func getCompressedPublicKeyFrom(uncompressedPublicKey: Data) throws -> Data { // Ensure the input key is exactly 64 bytes guard uncompressedPublicKey.count == 64 else { throw AccountError.invalidPrivateKey diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index b0788ef..4c52920 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -85,7 +85,9 @@ public class Wallet: Account, WalletInstance { data.append(parentKey.privateKey) } else { // Append the compressed public key - guard let publicKey = try? getCompressedPublicKey(privateKey: parentKey.privateKey) + guard + let publicKey = try? Wallet.getCompressedPublicKeyFrom( + privateKey: parentKey.privateKey) else { throw WalletError.failedToGetPublicKey } @@ -133,12 +135,4 @@ public class Wallet: Account, WalletInstance { // Return the new child key return Key(privateKey: childPrivateKey, chainCode: Data(R)) } - - private static func getCompressedPublicKey(privateKey: Data) throws -> Data { - guard let uncompressedPublicKey = XyoAddress(privateKey: privateKey.toHexString()).publicKeyBytes - else { - throw WalletError.failedToGetPublicKey - } - return try Account.getCompressedKeyFrom(uncompressedPublicKey: uncompressedPublicKey) - } } From 0d7e186b9cd81fc8e93112b3924799a0698a5392 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 08:22:16 -0600 Subject: [PATCH 27/33] Add test for BW Builder --- .../BoundWitness/BoundWitness.swift | 24 ----------- .../BoundWitness/BoundWitnessBuilder.swift | 40 +++++++++++++++++++ 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitness.swift b/Tests/XyoClientTests/BoundWitness/BoundWitness.swift index 86a5611..4852557 100644 --- a/Tests/XyoClientTests/BoundWitness/BoundWitness.swift +++ b/Tests/XyoClientTests/BoundWitness/BoundWitness.swift @@ -59,28 +59,4 @@ final class BoundWitnessTests: XCTestCase { XCTAssertEqual( bwJsonHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") } - - func testSequence_hash_returnsExpectedHash() throws { - for testCase in boundWitnessSequenceTestCases { - // Create accounts - let signers = testCase.addresses.map { Account.fromPrivateKey($0) } - // Ensure correct initial account state - for (i, previousHash) in testCase.previousHashes.enumerated() { - XCTAssertEqual(testCase.previousHashes[i], previousHash) - } - - // Build the BW - let bw = try BoundWitnessBuilder().signers(signers).payloads(testCase.payloads) - let (bwJson, _) = try bw.build() - let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) - - // Ensure the BW is correct - XCTAssertEqual(hash.toHex(), testCase.dataHash) - - // Ensure correct ending account state - for signer in signers { - XCTAssertEqual(signer.previousHash, hash) - } - } - } } diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift new file mode 100644 index 0000000..59353b2 --- /dev/null +++ b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift @@ -0,0 +1,40 @@ +import XCTest + +@testable import XyoClient + +@available(iOS 13.0, *) +final class BoundWitnessBuilderTests: XCTestCase { + static var allTests = [ + ("Build Returns Expected Hash", test_build_returnsExpectedHash), + ] + + override func setUp() { + super.setUp() + // Ensure previousHash = nil for tests addresses + Account.previousHashStore = MemoryPreviousHashStore() + } + + func test_build_returnsExpectedHash() throws { + for testCase in boundWitnessSequenceTestCases { + // Create accounts + let signers = testCase.addresses.map { Account.fromPrivateKey($0) } + // Ensure correct initial account state + for (i, previousHash) in testCase.previousHashes.enumerated() { + XCTAssertEqual(testCase.previousHashes[i], previousHash) + } + + // Build the BW + let bw = try BoundWitnessBuilder().signers(signers).payloads(testCase.payloads) + let (bwJson, _) = try bw.build() + let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) + + // Ensure the BW is correct + XCTAssertEqual(hash.toHex(), testCase.dataHash) + + // Ensure correct ending account state + for signer in signers { + XCTAssertEqual(signer.previousHash, hash) + } + } + } +} From eb5b281d36db7fa83aea425baee66352d1377917 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 08:22:24 -0600 Subject: [PATCH 28/33] Test wallets --- .../TestData/TestBoundWitnessSequence.swift | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift index 6c66390..091a77b 100644 --- a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift +++ b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift @@ -1,7 +1,8 @@ import XyoClient public struct BoundWitnessSequenceTestCase { - public var privateKeys: [String] + public var mnemonics: [String] + public var paths: [String] public var addresses: [Address] public var payloads: [EncodablePayloadInstance] public var payloadHashes: [String] @@ -21,8 +22,17 @@ let payloadSequences: [PayloadsWithHashes] = [ ] +let wallet1Mnemonic = "report door cry include salad horn recipe luxury access pledge husband maple busy double olive" +let wallet1Path = "m/44'/0'/0'/0" +let wallet1Address = "25524Ca99764D76CA27604Bb9727f6e2f27C4533" + +let wallet2Mnemonic = "turn you orphan sauce act patient village entire lava transfer height sense enroll quit idle" +let wallet2Path = "m/44'/0'/0'/0" +let wallet2Address = "e85c88A9943a2fF541865981b1DB8D2A8bF6bb78" + let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( - privateKeys: [], + mnemonics: [wallet1Mnemonic], + paths: [wallet1Path], addresses: [], payloads: payloadSequences[0].payloads, payloadHashes: payloadSequences[0].payloadHashes, @@ -31,7 +41,8 @@ let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( ) let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( - privateKeys: [], + mnemonics: [wallet2Mnemonic], + paths: [wallet2Path], addresses: [], payloads: payloadSequences[1].payloads, payloadHashes: payloadSequences[1].payloadHashes, @@ -40,7 +51,8 @@ let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( ) let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( - privateKeys: [], + mnemonics: [wallet1Mnemonic, wallet2Mnemonic], + paths: [wallet1Path, wallet2Path], addresses: [], payloads: payloadSequences[2].payloads, payloadHashes: payloadSequences[2].payloadHashes, From df62cf2a9263a600c6a8a5e78488d58c17325b17 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 08:59:38 -0600 Subject: [PATCH 29/33] Implement WalletStatic protocol on Wallet --- Sources/XyoClient/Wallet/Wallet.swift | 16 ++++++++++------ Sources/XyoClient/Wallet/WalletStatic.swift | 9 +++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index 4c52920..e5dce66 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -23,15 +23,10 @@ public enum WalletError: Error { case missingPublicKey } -public class Wallet: Account, WalletInstance { +public class Wallet: Account, WalletInstance, WalletStatic { static let defaultPath = "m/44'/60'/0'/0/0" - public func derivePath(path: String) throws -> any WalletInstance { - let key = try Wallet.deriveKey(from: self._key, path: path) - return try Wallet(key: key) - } - private var _key: Key init(key: Key) throws { @@ -49,6 +44,10 @@ public class Wallet: Account, WalletInstance { let derivedKey = try Wallet.deriveKey(from: rootKey, path: path) try self.init(key: derivedKey) } + + public static func fromMnemonic(mnemonic: String, path: String?) throws -> any WalletInstance { + return try Wallet(phrase: mnemonic, path: path ?? Wallet.defaultPath) + } static func deriveKey(from parentKey: Key, path: String) throws -> Key { let components = path.split(separator: "/") @@ -135,4 +134,9 @@ public class Wallet: Account, WalletInstance { // Return the new child key return Key(privateKey: childPrivateKey, chainCode: Data(R)) } + + public func derivePath(path: String) throws -> any WalletInstance { + let key = try Wallet.deriveKey(from: self._key, path: path) + return try Wallet(key: key) + } } diff --git a/Sources/XyoClient/Wallet/WalletStatic.swift b/Sources/XyoClient/Wallet/WalletStatic.swift index f8ad58e..ec03774 100644 --- a/Sources/XyoClient/Wallet/WalletStatic.swift +++ b/Sources/XyoClient/Wallet/WalletStatic.swift @@ -2,10 +2,7 @@ import Foundation public protocol WalletStatic { - // func create(config: XyoPayload) throws -> WalletInstance - // func fromExtendedKey(key: String) throws -> WalletInstance - // func fromMnemonic(mnemonic: String) throws -> WalletInstance - // func fromPhrase(mnemonic: String, path: String?) throws -> WalletInstance - // func fromSeed(seed: Data) throws -> WalletInstance - func random() -> WalletInstance + // static func fromExtendedKey(key: String) throws -> WalletInstance + static func fromMnemonic(mnemonic: String, path: String?) throws -> WalletInstance + // static func random() -> WalletInstance } From f1b3c2e85c9e42068c91a73c565daba5e9cce562 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 08:59:56 -0600 Subject: [PATCH 30/33] Use real test vectors --- .../BoundWitness/BoundWitnessBuilder.swift | 34 ++++++++++++++----- .../TestData/TestBoundWitnessSequence.swift | 20 ++++++----- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift index 59353b2..75a2e82 100644 --- a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift +++ b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift @@ -5,7 +5,7 @@ import XCTest @available(iOS 13.0, *) final class BoundWitnessBuilderTests: XCTestCase { static var allTests = [ - ("Build Returns Expected Hash", test_build_returnsExpectedHash), + ("Build Returns Expected Hash", test_build_returnsExpectedHash) ] override func setUp() { @@ -17,7 +17,23 @@ final class BoundWitnessBuilderTests: XCTestCase { func test_build_returnsExpectedHash() throws { for testCase in boundWitnessSequenceTestCases { // Create accounts - let signers = testCase.addresses.map { Account.fromPrivateKey($0) } + var signers: [AccountInstance] = [] + for (i, mnemonic) in testCase.mnemonics.enumerated() { + let path = testCase.paths[i] + if let account = try? Wallet.fromMnemonic(mnemonic: mnemonic, path:path){ + signers.append(account) + } else { + XCTAssertTrue(false, "Error creating account from mnemonic") + } + + } + XCTAssertEqual( + testCase.addresses.count, signers.count, "Incorrect number of accounts created.") + XCTAssertEqual( + testCase.addresses.compactMap { $0.lowercased() }, + signers.compactMap { $0.address?.toHex().lowercased() }, + "Incorrect addresses when creating accounts." + ) // Ensure correct initial account state for (i, previousHash) in testCase.previousHashes.enumerated() { XCTAssertEqual(testCase.previousHashes[i], previousHash) @@ -28,13 +44,13 @@ final class BoundWitnessBuilderTests: XCTestCase { let (bwJson, _) = try bw.build() let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) - // Ensure the BW is correct - XCTAssertEqual(hash.toHex(), testCase.dataHash) - - // Ensure correct ending account state - for signer in signers { - XCTAssertEqual(signer.previousHash, hash) - } +// // Ensure the BW is correct +// XCTAssertEqual(hash.toHex(), testCase.dataHash) +// +// // Ensure correct ending account state +// for signer in signers { +// XCTAssertEqual(signer.previousHash, hash) +// } } } } diff --git a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift index 091a77b..4d22301 100644 --- a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift +++ b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift @@ -3,7 +3,7 @@ import XyoClient public struct BoundWitnessSequenceTestCase { public var mnemonics: [String] public var paths: [String] - public var addresses: [Address] + public var addresses: [String] public var payloads: [EncodablePayloadInstance] public var payloadHashes: [String] public var previousHashes: [String?] @@ -22,18 +22,20 @@ let payloadSequences: [PayloadsWithHashes] = [ ] -let wallet1Mnemonic = "report door cry include salad horn recipe luxury access pledge husband maple busy double olive" -let wallet1Path = "m/44'/0'/0'/0" +let wallet1Mnemonic = + "report door cry include salad horn recipe luxury access pledge husband maple busy double olive" +let wallet1Path = "m/44'/60'/0'/0/0" let wallet1Address = "25524Ca99764D76CA27604Bb9727f6e2f27C4533" -let wallet2Mnemonic = "turn you orphan sauce act patient village entire lava transfer height sense enroll quit idle" -let wallet2Path = "m/44'/0'/0'/0" -let wallet2Address = "e85c88A9943a2fF541865981b1DB8D2A8bF6bb78" +let wallet2Mnemonic = + "turn you orphan sauce act patient village entire lava transfer height sense enroll quit idle" +let wallet2Path = "m/44'/60'/0'/0/0" +let wallet2Address = "FdCeD2c3549289049BeBf743fB721Df211633fBF" let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( mnemonics: [wallet1Mnemonic], paths: [wallet1Path], - addresses: [], + addresses: [wallet1Address], payloads: payloadSequences[0].payloads, payloadHashes: payloadSequences[0].payloadHashes, previousHashes: [nil, nil], @@ -43,7 +45,7 @@ let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( mnemonics: [wallet2Mnemonic], paths: [wallet2Path], - addresses: [], + addresses: [wallet2Address], payloads: payloadSequences[1].payloads, payloadHashes: payloadSequences[1].payloadHashes, previousHashes: [], @@ -53,7 +55,7 @@ let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( mnemonics: [wallet1Mnemonic, wallet2Mnemonic], paths: [wallet1Path, wallet2Path], - addresses: [], + addresses: [wallet1Address, wallet2Address], payloads: payloadSequences[2].payloads, payloadHashes: payloadSequences[2].payloadHashes, previousHashes: [], From ce55d4b3b365dc5a1bd72c77862d12ec19e075c7 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 09:05:05 -0600 Subject: [PATCH 31/33] Notes on assertions --- .../BoundWitness/BoundWitnessBuilder.swift | 17 ++++++++++------- .../TestData/TestBoundWitnessSequence.swift | 9 ++++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift index 75a2e82..03c5d09 100644 --- a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift +++ b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift @@ -44,13 +44,16 @@ final class BoundWitnessBuilderTests: XCTestCase { let (bwJson, _) = try bw.build() let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) -// // Ensure the BW is correct -// XCTAssertEqual(hash.toHex(), testCase.dataHash) -// -// // Ensure correct ending account state -// for signer in signers { -// XCTAssertEqual(signer.previousHash, hash) -// } + // Ensure the BW is correct + XCTAssertEqual(hash.toHex(), testCase.dataHash) + + // Ensure correct ending account state + for signer in signers { + XCTAssertEqual(signer.previousHash, hash) + } + + // TODO: Ensure previous hash is correct + } } } diff --git a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift index 4d22301..05a5aca 100644 --- a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift +++ b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift @@ -39,7 +39,7 @@ let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( payloads: payloadSequences[0].payloads, payloadHashes: payloadSequences[0].payloadHashes, previousHashes: [nil, nil], - dataHash: "" + dataHash: "750113b9826ba94b622667b06cd8467f1330837581c28907c16160fec20d0a4b" ) let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( @@ -49,7 +49,7 @@ let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( payloads: payloadSequences[1].payloads, payloadHashes: payloadSequences[1].payloadHashes, previousHashes: [], - dataHash: "" + dataHash: "bacd010d79126a154339e59c11c5b46be032c3bef65626f83bcafe968dc6dd1b" ) let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( @@ -58,7 +58,10 @@ let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( addresses: [wallet1Address, wallet2Address], payloads: payloadSequences[2].payloads, payloadHashes: payloadSequences[2].payloadHashes, - previousHashes: [], + previousHashes: [ + "750113b9826ba94b622667b06cd8467f1330837581c28907c16160fec20d0a4b", + "bacd010d79126a154339e59c11c5b46be032c3bef65626f83bcafe968dc6dd1b" + ], dataHash: "" ) From 92bfe34db669c50cbc27d9333ccb7dc74fee0720 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 14:35:04 -0600 Subject: [PATCH 32/33] Full chain tests --- Sources/XyoClient/Wallet/Wallet.swift | 4 +- .../BoundWitness/BoundWitnessBuilder.swift | 32 ++++++++--- .../TestData/TestBoundWitnessSequence.swift | 56 ++++++++++++++++--- 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/Sources/XyoClient/Wallet/Wallet.swift b/Sources/XyoClient/Wallet/Wallet.swift index e5dce66..9753e34 100644 --- a/Sources/XyoClient/Wallet/Wallet.swift +++ b/Sources/XyoClient/Wallet/Wallet.swift @@ -44,7 +44,7 @@ public class Wallet: Account, WalletInstance, WalletStatic { let derivedKey = try Wallet.deriveKey(from: rootKey, path: path) try self.init(key: derivedKey) } - + public static func fromMnemonic(mnemonic: String, path: String?) throws -> any WalletInstance { return try Wallet(phrase: mnemonic, path: path ?? Wallet.defaultPath) } @@ -134,7 +134,7 @@ public class Wallet: Account, WalletInstance, WalletStatic { // Return the new child key return Key(privateKey: childPrivateKey, chainCode: Data(R)) } - + public func derivePath(path: String) throws -> any WalletInstance { let key = try Wallet.deriveKey(from: self._key, path: path) return try Wallet(key: key) diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift index 03c5d09..4170f94 100644 --- a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift +++ b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift @@ -20,15 +20,17 @@ final class BoundWitnessBuilderTests: XCTestCase { var signers: [AccountInstance] = [] for (i, mnemonic) in testCase.mnemonics.enumerated() { let path = testCase.paths[i] - if let account = try? Wallet.fromMnemonic(mnemonic: mnemonic, path:path){ + if let account = try? Wallet.fromMnemonic(mnemonic: mnemonic, path: path) { signers.append(account) } else { XCTAssertTrue(false, "Error creating account from mnemonic") } - } XCTAssertEqual( - testCase.addresses.count, signers.count, "Incorrect number of accounts created.") + testCase.addresses.count, + signers.count, + "Incorrect number of accounts created." + ) XCTAssertEqual( testCase.addresses.compactMap { $0.lowercased() }, signers.compactMap { $0.address?.toHex().lowercased() }, @@ -36,7 +38,11 @@ final class BoundWitnessBuilderTests: XCTestCase { ) // Ensure correct initial account state for (i, previousHash) in testCase.previousHashes.enumerated() { - XCTAssertEqual(testCase.previousHashes[i], previousHash) + XCTAssertEqual( + testCase.previousHashes[i], + previousHash, + "Incorrect previous hash for account" + ) } // Build the BW @@ -45,15 +51,23 @@ final class BoundWitnessBuilderTests: XCTestCase { let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) // Ensure the BW is correct - XCTAssertEqual(hash.toHex(), testCase.dataHash) + XCTAssertEqual(hash.toHex(), testCase.dataHash, "Incorrect data hash in BW") + for (i, expectedPayloadHash) in testCase.payloadHashes.enumerated() { + let actualPayloadHash = bwJson.typedPayload.payload_hashes[i] + // Ensure payload hash is correct + XCTAssertEqual(expectedPayloadHash, actualPayloadHash, "Incorrect payload hash in BW") + } + for (i, payload) in testCase.payloads.enumerated() { + let actualSchema = bwJson.typedPayload.payload_schemas[i] + // Ensure payload hash is correct + XCTAssertEqual(payload.schema, actualSchema, "Incorrect payload schema in BW") + } // Ensure correct ending account state for signer in signers { - XCTAssertEqual(signer.previousHash, hash) + // Ensure previous hash is correct + XCTAssertEqual(signer.previousHash, hash, "Incorrect previous hash for account") } - - // TODO: Ensure previous hash is correct - } } } diff --git a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift index 05a5aca..fae2bb8 100644 --- a/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift +++ b/Tests/XyoClientTests/TestData/TestBoundWitnessSequence.swift @@ -16,10 +16,32 @@ public struct PayloadsWithHashes { } let payloadSequences: [PayloadsWithHashes] = [ - .init(payloads: [IdPayload(0)], payloadHashes: [""]), - .init(payloads: [IdPayload(1)], payloadHashes: [""]), - .init(payloads: [IdPayload(2), IdPayload(3)], payloadHashes: ["", ""]), - + .init( + payloads: [IdPayload(0)], + payloadHashes: [ + "ada56ff753c0c9b2ce5e1f823eda9ac53501db2843d8883d6cf6869c18ef7f65" + ] + ), + .init( + payloads: [IdPayload(1)], + payloadHashes: [ + "3a3b8deca568ff820b0b7c8714fbdf82b40fb54f4b15aca8745e06b81291558e" + ] + ), + .init( + payloads: [IdPayload(2), IdPayload(3)], + payloadHashes: [ + "1a40207fab71fc184e88557d5bee6196cbbb49f11f73cda85000555a628a8f0a", + "c4bce9b4d3239fcc9a248251d1bef1ba7677e3c0c2c43ce909a6668885b519e6", + ] + ), + .init( + payloads: [IdPayload(4), IdPayload(5)], + payloadHashes: [ + "59c0374dd801ae64ddddba27320ca028d7bd4b3d460f6674c7da1b4aa9c956d6", + "5d9b8e84bc824280fcbb6290904c2edbb401d626ad9789717c0a23d1cab937b0", + ] + ), ] let wallet1Mnemonic = @@ -38,7 +60,7 @@ let boundWitnessSequenceTestCase1: BoundWitnessSequenceTestCase = .init( addresses: [wallet1Address], payloads: payloadSequences[0].payloads, payloadHashes: payloadSequences[0].payloadHashes, - previousHashes: [nil, nil], + previousHashes: [nil], dataHash: "750113b9826ba94b622667b06cd8467f1330837581c28907c16160fec20d0a4b" ) @@ -48,7 +70,7 @@ let boundWitnessSequenceTestCase2: BoundWitnessSequenceTestCase = .init( addresses: [wallet2Address], payloads: payloadSequences[1].payloads, payloadHashes: payloadSequences[1].payloadHashes, - previousHashes: [], + previousHashes: [nil], dataHash: "bacd010d79126a154339e59c11c5b46be032c3bef65626f83bcafe968dc6dd1b" ) @@ -60,11 +82,27 @@ let boundWitnessSequenceTestCase3: BoundWitnessSequenceTestCase = .init( payloadHashes: payloadSequences[2].payloadHashes, previousHashes: [ "750113b9826ba94b622667b06cd8467f1330837581c28907c16160fec20d0a4b", - "bacd010d79126a154339e59c11c5b46be032c3bef65626f83bcafe968dc6dd1b" + "bacd010d79126a154339e59c11c5b46be032c3bef65626f83bcafe968dc6dd1b", + ], + dataHash: "73245ef73517913f4b57c12d56d81199968ecd8fbefea9ddc474f43dd6cfa8c8" +) + +let boundWitnessSequenceTestCase4: BoundWitnessSequenceTestCase = .init( + mnemonics: [wallet1Mnemonic, wallet2Mnemonic], + paths: [wallet1Path, wallet2Path], + addresses: [wallet1Address, wallet2Address], + payloads: payloadSequences[3].payloads, + payloadHashes: payloadSequences[3].payloadHashes, + previousHashes: [ + "73245ef73517913f4b57c12d56d81199968ecd8fbefea9ddc474f43dd6cfa8c8", + "73245ef73517913f4b57c12d56d81199968ecd8fbefea9ddc474f43dd6cfa8c8", ], - dataHash: "" + dataHash: "210d86ea43d82b85a49b77959a8ee4e6016ff7036254cfa39953befc66073010" ) let boundWitnessSequenceTestCases = [ - boundWitnessSequenceTestCase1, boundWitnessSequenceTestCase2, boundWitnessSequenceTestCase3, + boundWitnessSequenceTestCase1, + boundWitnessSequenceTestCase2, + boundWitnessSequenceTestCase3, + boundWitnessSequenceTestCase4, ] From 51076a457e6013a201619031fcc34f25c6c34b7c Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 5 Dec 2024 15:35:51 -0600 Subject: [PATCH 33/33] Remove legacy test cases --- .../BoundWitness/BoundWitness.swift | 62 ------------------- .../BoundWitness/BoundWitnessBuilder.swift | 4 +- 2 files changed, 3 insertions(+), 63 deletions(-) delete mode 100644 Tests/XyoClientTests/BoundWitness/BoundWitness.swift diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitness.swift b/Tests/XyoClientTests/BoundWitness/BoundWitness.swift deleted file mode 100644 index 4852557..0000000 --- a/Tests/XyoClientTests/BoundWitness/BoundWitness.swift +++ /dev/null @@ -1,62 +0,0 @@ -import XCTest - -@testable import XyoClient - -@available(iOS 13.0, *) -final class BoundWitnessTests: XCTestCase { - static var allTests = [ - ("testPayload_hash_returnsExpectedHash1", testPayload_hash_returnsExpectedHash1), - ("testPayload_hash_returnsExpectedHash2", testPayload_hash_returnsExpectedHash2), - ] - - override func setUp() { - super.setUp() - // Ensure previousHash = nil for tests addresses - Account.previousHashStore = MemoryPreviousHashStore() - } - - func testPayload_hash_returnsExpectedHash1() throws { - let payloadDataHash = try PayloadBuilder.dataHash(from: testPayload1) - XCTAssertEqual(payloadDataHash, testPayload1Hash) - let address = Account.fromPrivateKey(testVectorPrivateKey.hexToData()!) - XCTAssertEqual(testVectorPrivateKey, address.privateKey!.toHex()) - XCTAssertEqual(testVectorAddress, address.address!.toHex()) - let bw = try BoundWitnessBuilder().signer(address).payload( - "network.xyo.test", TestPayload1("network.xyo.test")) - let (bwJson, _) = try bw.build() - let hash = try PayloadBuilder.hash(fromWithMeta: bwJson) - let dataHash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) - XCTAssertEqual( - dataHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") - XCTAssertEqual( - hash.toHex(), "c267291c8169e428aaedbbf52792f9378ee03910401ef882b653a75f85370722") - } - - func testPayload_hash_returnsExpectedHash2() throws { - let hash = try PayloadBuilder.dataHash(from: testPayload2) - XCTAssertEqual(hash.toHex(), testPayload2Hash.toHex()) - let address = Account.fromPrivateKey(testVectorPrivateKey.hexToData()!) - let bw = try BoundWitnessBuilder().signer(address).payload("network.xyo.test", testPayload2) - let (bwJson, _) = try bw.build() - let bwJsonHash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) - let e = try? bwJson.toJson() - if e != nil { - print("\n") - print(e!) - print("\n") - } - XCTAssertEqual( - bwJsonHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") - } - - func testPayload_hash_returnsExpectedHashWhenNested() throws { - let hash = try PayloadBuilder.dataHash(from: testPayload2) - XCTAssertEqual(hash, testPayload2Hash) - let address = Account.fromPrivateKey(testVectorPrivateKey.hexToData()!) - let bw = try BoundWitnessBuilder().signer(address).payload("network.xyo.test", testPayload2) - let (bwJson, _) = try bw.build() - let bwJsonHash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) - XCTAssertEqual( - bwJsonHash.toHex(), "6f731b3956114fd0d18820dbbe1116f9e36dc8d803b0bb049302f7109037468f") - } -} diff --git a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift index 4170f94..3169abd 100644 --- a/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift +++ b/Tests/XyoClientTests/BoundWitness/BoundWitnessBuilder.swift @@ -49,13 +49,15 @@ final class BoundWitnessBuilderTests: XCTestCase { let bw = try BoundWitnessBuilder().signers(signers).payloads(testCase.payloads) let (bwJson, _) = try bw.build() let hash = try PayloadBuilder.dataHash(from: bwJson.typedPayload) + let rootHash = try PayloadBuilder.hash(from: bwJson.typedPayload) // Ensure the BW is correct XCTAssertEqual(hash.toHex(), testCase.dataHash, "Incorrect data hash in BW") for (i, expectedPayloadHash) in testCase.payloadHashes.enumerated() { let actualPayloadHash = bwJson.typedPayload.payload_hashes[i] // Ensure payload hash is correct - XCTAssertEqual(expectedPayloadHash, actualPayloadHash, "Incorrect payload hash in BW") + XCTAssertEqual( + expectedPayloadHash, actualPayloadHash, "Incorrect payload hash in BW") } for (i, payload) in testCase.payloads.enumerated() { let actualSchema = bwJson.typedPayload.payload_schemas[i]