├── .swift-version ├── logo.png ├── .gitignore ├── Tests ├── LinuxMain.swift └── XRPKitTests │ ├── XCTestManifests.swift │ └── WebSocketTester.swift ├── test_linux.sh ├── .travis.yml ├── Sources └── XRPKit │ ├── Models │ ├── XRPKeypair.swift │ ├── XRPAccountInfo.swift │ ├── XRPHistoricalTransaction.swift │ ├── XRPAccount.swift │ ├── XRPCurrentLedgerInfo.swift │ ├── Transactions │ │ ├── XRPEscrowCancel.swift │ │ ├── XRPEscrowFinish.swift │ │ ├── XRPAccountSet.swift │ │ ├── XRPPayment.swift │ │ ├── XRPSignerListSet.swift │ │ ├── XRPEscrowCreate.swift │ │ ├── XRPTransaction.swift │ │ └── XRPRawTransaction.swift │ ├── XRPLResponse.swift │ ├── XRPAmount.swift │ ├── XRPAddress.swift │ └── XRPWallet.swift │ ├── SigningAlgorithms │ ├── SigningAlgorithm.swift │ ├── ED25519.swift │ ├── ED25519 (pebble8888) │ │ ├── ed25519_utility.swift │ │ ├── ed25519_verify.swift │ │ ├── ed25519_sign.swift │ │ ├── ed25519_sc.swift │ │ └── ed25519_fe.swift │ └── secp256K1.swift │ ├── HDWallet │ ├── DerivationNode.swift │ ├── BigInt │ │ ├── BigInt+Extension.swift │ │ ├── VarInt.swift │ │ └── BigNumber.swift │ ├── Coin.swift │ └── PrivateKey.swift │ ├── Utilities │ ├── Entropy.swift │ ├── URandom.swift │ ├── Base58.swift │ ├── IssuedAmount.swift │ ├── Bip39Mnemonic.swift │ ├── Extensions.swift │ ├── RIPEMD160.swift │ ├── Serializer.swift │ └── SerializerDefinitions.swift │ └── Network │ ├── WebSocket+Convenience.swift │ ├── HTTP.swift │ ├── WebSocket.swift │ └── XRPLedger.swift ├── LICENSE ├── Package.swift ├── Package.resolved ├── XRPKit.podspec └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.1.1 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MitchLang009/XRPKit/HEAD/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | .swiftpm 3 | XRPKit.xcodeproj 4 | !.gitignore 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import XRPKitTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += XRPKitTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/XRPKitTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(XRPKitTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /test_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run --rm \ 4 | --volume "$(pwd):/package" \ 5 | --workdir "/package" \ 6 | swift:5.1.3 \ 7 | /bin/bash -c \ 8 | "swift package resolve && swift test --build-path ./.build/linux" 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | language: generic 5 | sudo: required 6 | dist: trusty 7 | osx_image: xcode11.3 8 | install: 9 | - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" 10 | script: 11 | - swift build 12 | - swift test 13 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPKeypair.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keypair.swift 3 | // BigInt 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | 10 | struct XRPKeyPair { 11 | var privateKey: String 12 | var publicKey: String 13 | } 14 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPAccountInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPAccountInfo.swift 3 | // BigInt 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct XRPAccountInfo { 11 | public var address: String 12 | public var drops: Int 13 | public var sequence: Int 14 | } 15 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPHistoricalTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPHistoricalTransaction.swift 3 | // BigInt 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct XRPHistoricalTransaction { 11 | public var type: String 12 | public var address: String 13 | public var amount: XRPAmount 14 | public var date: Date 15 | public var raw: NSDictionary 16 | } 17 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPAccount.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPAccount.swift 3 | // BigInt 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct XRPAccount: Codable { 11 | public var address: String 12 | public var secret: String 13 | 14 | public init(address: String, secret: String) { 15 | self.address = address 16 | self.secret = secret 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/XRPKit/SigningAlgorithms/SigningAlgorithm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Mitch Lang on 1/31/20. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol SigningAlgorithm { 11 | static func deriveKeyPair(seed: [UInt8]) throws -> XRPKeyPair 12 | static func sign(message: [UInt8], privateKey: [UInt8]) throws -> [UInt8] 13 | static func verify(signature: [UInt8], message: [UInt8], publicKey: [UInt8]) throws -> Bool 14 | } 15 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPCurrentLedgerInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPCurrentLedgerInfo.swift 3 | // BigInt 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct XRPCurrentLedgerInfo { 11 | 12 | public init(index: Int, minFee: Int, maxFee: Int) { 13 | self.index = index 14 | self.minFee = minFee 15 | self.maxFee = maxFee 16 | } 17 | 18 | public var index: Int 19 | public var minFee: Int 20 | public var maxFee: Int 21 | } 22 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPEscrowCancel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPEscrowCancel.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/5/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public class XRPEscrowCancel: XRPTransaction { 11 | 12 | public init(using wallet: XRPWallet, owner: String, offerSequence: UInt32) { 13 | 14 | // dictionary containing partial transaction fields 15 | let _fields: [String:Any] = [ 16 | "TransactionType": "EscrowCancel", 17 | "OfferSequence": offerSequence, 18 | "Owner": owner, 19 | ] 20 | 21 | super.init(wallet: wallet, fields: _fields) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPEscrowFinish.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPEscrowFinish.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/5/20. 6 | // 7 | 8 | import Foundation 9 | import NIO 10 | 11 | public class XRPEscrowFinish: XRPTransaction { 12 | 13 | public init(using wallet: XRPWallet, owner: String, offerSequence: UInt32) { 14 | 15 | // dictionary containing partial transaction fields 16 | let _fields: [String:Any] = [ 17 | "TransactionType": "EscrowFinish", 18 | "OfferSequence": offerSequence, 19 | "Owner": owner, 20 | ] 21 | 22 | super.init(wallet: wallet, fields: _fields) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/XRPKit/HDWallet/DerivationNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DerivationNode.swift 3 | // CryptoSwift 4 | // 5 | // Created by Pavlo Boiko on 02.07.18. 6 | // 7 | 8 | import Foundation 9 | 10 | internal enum DerivationNode { 11 | case hardened(UInt32) 12 | case notHardened(UInt32) 13 | 14 | internal var index: UInt32 { 15 | switch self { 16 | case .hardened(let index): 17 | return index 18 | case .notHardened(let index): 19 | return index 20 | } 21 | } 22 | 23 | internal var hardens: Bool { 24 | switch self { 25 | case .hardened: 26 | return true 27 | case .notHardened: 28 | return false 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/Entropy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Entropy.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/23/19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum EntropyError: Error { 11 | case invalidBufferSize 12 | } 13 | 14 | class Entropy { 15 | 16 | private(set) var bytes: [UInt8]! 17 | 18 | init() { 19 | var _bytes = [UInt8](repeating: 0, count: 16) 20 | _bytes = try! URandom().bytes(count: 16) 21 | // let status = SecRandomCopyBytes(kSecRandomDefault, _bytes.count, &_bytes) 22 | // 23 | // if status != errSecSuccess { // Always test the status. 24 | // fatalError() 25 | // } 26 | self.bytes = _bytes 27 | } 28 | 29 | init(bytes: [UInt8]) { 30 | self.bytes = bytes 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPAccountSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPDisableMaster.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/10/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum XRPAccountSetFlag: UInt32 { 11 | case asfDisableMaster = 4 12 | } 13 | 14 | public class XRPAccountSet: XRPTransaction { 15 | 16 | public init(wallet: XRPWallet, set: XRPAccountSetFlag) { 17 | let _fields: [String:Any] = [ 18 | "TransactionType" : "AccountSet", 19 | "SetFlag" : set.rawValue 20 | ] 21 | super.init(wallet: wallet, fields: _fields) 22 | } 23 | 24 | public init(wallet: XRPWallet, clear: XRPAccountSetFlag) { 25 | let _fields: [String:Any] = [ 26 | "TransactionType" : "AccountSet", 27 | "ClearFlag" : clear.rawValue 28 | ] 29 | super.init(wallet: wallet, fields: _fields) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPPayment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPPaymentTransaction.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/4/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public class XRPPayment: XRPTransaction { 11 | 12 | public init(from wallet: XRPWallet, to address: XRPAddress, amount: XRPAmount, sourceTag : UInt32? = nil) { 13 | var _fields: [String:Any] = [ 14 | "TransactionType" : "Payment", 15 | "Destination" : address.rAddress, 16 | "Amount" : String(amount.drops), 17 | "Flags" : UInt64(2147483648), 18 | ] 19 | 20 | if let destinationTag = address.tag { 21 | _fields["DestinationTag"] = destinationTag 22 | } 23 | 24 | if let sourceTag = sourceTag { 25 | _fields["SourceTag"] = sourceTag 26 | } 27 | 28 | super.init(wallet: wallet, fields: _fields) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Tests/XRPKitTests/WebSocketTester.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | import XRPKit 10 | 11 | class WebSocketTester: XRPWebSocketDelegate { 12 | 13 | var completion: (XRPWebSocketResponse)->() 14 | 15 | init(completion: @escaping (XRPWebSocketResponse)->()) { 16 | self.completion = completion 17 | } 18 | 19 | func onConnected(connection: XRPWebSocket) { 20 | print("onConnected") 21 | } 22 | 23 | func onDisconnected(connection: XRPWebSocket, error: Error?) { 24 | print("onDisconnected") 25 | } 26 | 27 | func onError(connection: XRPWebSocket, error: Error) { 28 | print("onError") 29 | } 30 | 31 | func onResponse(connection: XRPWebSocket, response: XRPWebSocketResponse) { 32 | self.completion(response) 33 | } 34 | 35 | func onStream(connection: XRPWebSocket, object: NSDictionary) { 36 | print(object) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/XRPKit/SigningAlgorithms/ED25519.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ED25519.swift 3 | // 4 | // Created by Mitch Lang on 10/24/19. 5 | // 6 | 7 | import Foundation 8 | 9 | class ED25519: SigningAlgorithm { 10 | 11 | static func deriveKeyPair(seed: [UInt8]) throws -> XRPKeyPair { 12 | let privateKey = [UInt8](Data(seed).sha512().prefix(32)) 13 | let publicKey = Ed25519.calcPublicKey(secretKey: privateKey) 14 | return XRPKeyPair(privateKey: privateKey.toHexString(), publicKey: publicKey.toHexString()) 15 | } 16 | 17 | static func sign(message: [UInt8], privateKey: [UInt8]) throws -> [UInt8] { 18 | return Ed25519.sign(message: message, secretKey: privateKey) 19 | } 20 | 21 | static func verify(signature: [UInt8], message: [UInt8], publicKey: [UInt8]) throws -> Bool { 22 | // remove 1 byte prefix from public key 23 | let publicKey = [UInt8](publicKey.suffix(from: 1)) 24 | return Ed25519.verify(signature: signature, message: message, publicKey: publicKey) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Sources/XRPKit/Network/WebSocket+Convenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | 10 | #if canImport(WebSocketKit) 11 | extension LinuxWebSocket { 12 | public func subscribe(account: String) { 13 | let parameters: [String : Any] = [ 14 | "id": UUID().uuidString, 15 | "command": "subscribe", 16 | "accounts": [account] 17 | ] 18 | let data = try! JSONSerialization.data(withJSONObject: parameters, options: []) 19 | self.send(data: data) 20 | } 21 | } 22 | #elseif !os(Linux) 23 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 24 | extension AppleWebSocket { 25 | public func subscribe(account: String) { 26 | let parameters: [String : Any] = [ 27 | "id": UUID().uuidString, 28 | "command": "subscribe", 29 | "accounts": [account] 30 | ] 31 | let data = try! JSONSerialization.data(withJSONObject: parameters, options: []) 32 | self.send(data: data) 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 MitchLang009 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPSignerListSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPSignerListSet.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/10/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct XRPSignerEntry { 11 | var Account: String 12 | var SignerWeight: Int 13 | } 14 | 15 | public class XRPSignerListSet: XRPTransaction { 16 | 17 | public init(wallet: XRPWallet, signerQuorum: UInt32, signerEntries: [XRPSignerEntry]) { 18 | 19 | let signers = signerEntries.map { (signerEntry) -> NSDictionary in 20 | return NSDictionary(dictionary: [ 21 | "SignerEntry" : NSDictionary(dictionary: [ 22 | "Account" : signerEntry.Account, 23 | "SignerWeight" : signerEntry.SignerWeight, 24 | ]) 25 | ]) 26 | } 27 | 28 | // dictionary containing partial transaction fields 29 | let _fields: [String:Any] = [ 30 | "TransactionType": "SignerListSet", 31 | "SignerQuorum": signerQuorum, 32 | "SignerEntries": signers 33 | ] 34 | 35 | super.init(wallet: wallet, fields: _fields) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPEscrowCreate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPEscrowCreate.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/4/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public class XRPEscrowCreate: XRPTransaction { 11 | 12 | public init(from wallet: XRPWallet, to address: XRPAddress, amount: XRPAmount, finishAfter: Date, cancelAfter: Date?, sourceTag : UInt32? = nil) { 13 | 14 | // dictionary containing partial transaction fields 15 | var _fields: [String:Any] = [ 16 | "TransactionType": "EscrowCreate", 17 | "FinishAfter": finishAfter.timeIntervalSinceRippleEpoch, 18 | "Amount": String(amount.drops), 19 | "Destination": address.rAddress, 20 | ] 21 | 22 | if let cancelAfter = cancelAfter { 23 | assert(cancelAfter > finishAfter) 24 | _fields["CancelAfter"] = cancelAfter.timeIntervalSinceRippleEpoch 25 | } 26 | 27 | if let destinationTag = address.tag { 28 | _fields["DestinationTag"] = destinationTag 29 | } 30 | 31 | if let sourceTag = sourceTag { 32 | _fields["SourceTag"] = sourceTag 33 | } 34 | 35 | super.init(wallet: wallet, fields: _fields) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/URandom.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum URandomError: Swift.Error { 4 | case open(Int32) 5 | case read(Int32) 6 | } 7 | 8 | public final class URandom { 9 | 10 | private let file: UnsafeMutablePointer 11 | private let path = "/dev/urandom" 12 | 13 | public init() throws { 14 | guard let file = fopen(path, "rb") else { 15 | // The Random protocol doesn't allow init to fail, so we have to 16 | // check whether /dev/urandom was successfully opened here 17 | throw URandomError.open(errno) 18 | } 19 | self.file = file 20 | } 21 | 22 | deinit { 23 | fclose(file) 24 | } 25 | 26 | private func read(numBytes: Int) throws -> [UInt8] { 27 | 28 | 29 | // Initialize an empty array with space for numBytes bytes 30 | var bytes = [UInt8](repeating: 0, count: numBytes) 31 | guard fread(&bytes, 1, numBytes, file) == numBytes else { 32 | // If the requested number of random bytes couldn't be read, 33 | // we need to throw an error 34 | throw URandomError.read(errno) 35 | } 36 | 37 | return bytes 38 | } 39 | 40 | /// Get a random array of Bytes 41 | public func bytes(count: Int) throws -> [UInt8] { 42 | return try read(numBytes: count) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPLResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPLResponse.swift 3 | // BigInt 4 | // 5 | // Created by Mitch Lang on 2/3/20. 6 | // 7 | 8 | import Foundation 9 | import AnyCodable 10 | 11 | 12 | //public struct XRPWebSocketResponse: Codable { 13 | // public var id: String 14 | // public var status: String 15 | // public var type: String 16 | // public var result: T 17 | //} 18 | 19 | public struct XRPWebSocketResponse: Codable{ 20 | public let id: String 21 | public let status: String 22 | public let type: String 23 | private let _result: AnyCodable 24 | public var result: [String:AnyObject] { 25 | return _result.value as! [String:AnyObject] 26 | } 27 | 28 | enum CodingKeys: String, CodingKey { 29 | case id 30 | case status 31 | case type 32 | case _result = "result" 33 | } 34 | 35 | public init(from decoder: Decoder) throws { 36 | let values = try decoder.container(keyedBy: CodingKeys.self) 37 | id = try values.decode(String.self, forKey: .id) 38 | status = try values.decode(String.self, forKey: .status) 39 | type = try values.decode(String.self, forKey: .type) 40 | _result = try values.decode(AnyCodable.self, forKey: ._result) 41 | } 42 | } 43 | 44 | public struct XRPJsonRpcResponse { 45 | public var result: T 46 | } 47 | -------------------------------------------------------------------------------- /Sources/XRPKit/Network/HTTP.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Mitch Lang on 1/30/20. 6 | // 7 | 8 | import Foundation 9 | import NIO 10 | #if canImport(FoundationNetworking) 11 | import FoundationNetworking 12 | #endif 13 | #if canImport(CoreFoundation) 14 | import CoreFoundation 15 | #endif 16 | 17 | let eventGroup = MultiThreadedEventLoopGroup(numberOfThreads: 4) 18 | 19 | class HTTP { 20 | 21 | // http call to test linux cross platform 22 | static func post(url: URL, parameters: [String: Any]) -> EventLoopFuture { 23 | 24 | let promise = eventGroup.next().makePromise(of: Any.self) 25 | 26 | let httpBody = try! JSONSerialization.data(withJSONObject: parameters, options: []) 27 | var request = URLRequest(url: url) 28 | request.httpMethod = "POST" 29 | request.setValue("Application/json", forHTTPHeaderField: "Content-Type") 30 | request.httpBody = httpBody 31 | 32 | let session = URLSession.shared 33 | session.dataTask(with: request) { (data, response, error) in 34 | if let error = error { 35 | promise.fail(error) 36 | } 37 | // if let response = response { 38 | // print(response) 39 | // } 40 | if let data = data { 41 | do { 42 | let json = try JSONSerialization.jsonObject(with: data, options: []) 43 | promise.succeed(json) 44 | } catch { 45 | promise.fail(error) 46 | } 47 | } 48 | }.resume() 49 | 50 | return promise.futureResult 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/XRPKit/HDWallet/BigInt/BigInt+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BigInt+Extension.swift 3 | // WalletKit 4 | // 5 | // Created by yuzushioh on 2018/01/24. 6 | // Copyright © 2018 yuzushioh. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension BInt { 12 | var data: Data { 13 | let count = limbs.count 14 | var data = Data(count: count * 8) 15 | data.withUnsafeMutableBytes { (pointer) -> Void in 16 | guard var p = pointer.bindMemory(to: UInt8.self).baseAddress else { return } 17 | for i in (0..> UInt64(j * 8)) & 0xff) 20 | p += 1 21 | } 22 | } 23 | } 24 | 25 | return data 26 | } 27 | 28 | init?(hex: String) { 29 | self.init(number: hex.lowercased(), withBase: 16) 30 | } 31 | 32 | init(data: Data) { 33 | let n = data.count 34 | guard n > 0 else { 35 | self.init(0) 36 | return 37 | } 38 | 39 | let m = (n + 7) / 8 40 | var limbs = Limbs(repeating: 0, count: m) 41 | data.withUnsafeBytes { (ptr) -> Void in 42 | guard var p = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return } 43 | let r = n % 8 44 | let k = r == 0 ? 8 : r 45 | for j in (0.. 1 else { return } 50 | for i in (0..<(m - 1)).reversed() { 51 | for j in (0..<8).reversed() { 52 | limbs[i] += UInt64(p.pointee) << UInt64(j * 8) 53 | p += 1 54 | } 55 | } 56 | } 57 | 58 | self.init(limbs: limbs) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "XRPKit", 8 | platforms: [ 9 | .macOS(.v10_14), 10 | .iOS(.v10), 11 | .tvOS(.v10), 12 | .watchOS(.v3), 13 | ], 14 | products: [ 15 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 16 | .library( 17 | name: "XRPKit", 18 | targets: ["XRPKit"]), 19 | ], 20 | dependencies: [ 21 | // Dependencies declare other packages that this package depends on. 22 | // .package(url: /* package url */, from: "1.0.0"), 23 | .package(url: "https://github.com/Boilertalk/secp256k1.swift.git", from: "0.1.4"), 24 | .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.3.0")), 25 | .package(url: "https://github.com/Boilertalk/BigInt.swift.git", from: "1.0.0"), 26 | .package(url: "https://github.com/Flight-School/AnyCodable.git", from: "0.2.3"), 27 | // .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), 28 | .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"), 29 | .package(url: "https://github.com/vapor/websocket-kit.git", from: "2.0.0-beta.2.3"), 30 | ], 31 | targets: [ 32 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 33 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 34 | .target( 35 | name: "XRPKit", 36 | dependencies: ["WebSocketKit","NIO", "AnyCodable", "secp256k1", "CryptoSwift", "BigInt"]), 37 | .testTarget( 38 | name: "XRPKitTests", 39 | dependencies: ["XRPKit"]), 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/Base58.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseX.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/3/19. 6 | // 7 | 8 | import Foundation 9 | import BigInt 10 | 11 | public enum Base58String { 12 | public static let btcAlphabet = [UInt8]("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".utf8) 13 | public static let xrpAlphabet = [UInt8]("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz".utf8) 14 | } 15 | 16 | extension String { 17 | 18 | public init(base58Encoding bytes: Data, alphabet: [UInt8] = Base58String.xrpAlphabet) { 19 | var x = BigUInt(bytes) 20 | let radix = BigUInt(alphabet.count) 21 | 22 | var answer = [UInt8]() 23 | answer.reserveCapacity(bytes.count) 24 | 25 | while x > 0 { 26 | let (quotient, modulus) = x.quotientAndRemainder(dividingBy: radix) 27 | answer.append(alphabet[Int(modulus)]) 28 | x = quotient 29 | } 30 | 31 | let prefix = Array(bytes.prefix(while: {$0 == 0})).map { _ in alphabet[0] } 32 | answer.append(contentsOf: prefix) 33 | answer.reverse() 34 | 35 | self = String(bytes: answer, encoding: String.Encoding.utf8)! 36 | } 37 | 38 | } 39 | 40 | extension Data { 41 | 42 | init?(base58Decoding string: String, alphabet: [UInt8] = Base58String.xrpAlphabet) { 43 | var answer = BigUInt(0) 44 | var j = BigUInt(1) 45 | let radix = BigUInt(alphabet.count) 46 | let byteString = [UInt8](string.utf8) 47 | 48 | for ch in byteString.reversed() { 49 | if let index = alphabet.firstIndex(of: ch) { 50 | answer = answer + (j * BigUInt(index)) 51 | j *= radix 52 | } else { 53 | return nil 54 | } 55 | } 56 | 57 | let bytes = answer.serialize() 58 | self = byteString.prefix(while: { i in i == alphabet[0]}) + bytes 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPAmount.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPAmount.swift 3 | // 4 | // Created by Mitch Lang on 5/23/19. 5 | // 6 | 7 | import Foundation 8 | 9 | public enum XRPAmountError: Error { 10 | case invalidAmount 11 | } 12 | 13 | public struct XRPAmount { 14 | 15 | private(set) var drops: Int! 16 | 17 | public init(drops: Int) throws { 18 | if drops < 0 || drops > UInt64(100000000000000000) { 19 | throw XRPAmountError.invalidAmount 20 | } 21 | self.drops = drops 22 | } 23 | 24 | public init(_ text: String) throws { 25 | // removed commas 26 | let stripped = text.replacingOccurrences(of: ",", with: "") 27 | if !stripped.replacingOccurrences(of: ".", with: "").isNumber { 28 | throw XRPAmountError.invalidAmount 29 | } 30 | // get parts 31 | var xrp = stripped 32 | var drops = "" 33 | if let decimalIndex = stripped.firstIndex(of: ".") { 34 | xrp = String(stripped.prefix(upTo: decimalIndex)) 35 | let _index = stripped.index(decimalIndex, offsetBy: 1) 36 | drops = String(stripped.suffix(from: _index)) 37 | } 38 | // adjust values 39 | drops = drops + String.init(repeating: "0", count: 6-drops.count) 40 | // combine parts 41 | let _drops = Int(xrp+drops)! 42 | if _drops < 0 || _drops > UInt64(100000000000000000) { 43 | throw XRPAmountError.invalidAmount 44 | } 45 | self.drops = _drops 46 | 47 | } 48 | 49 | public func prettyPrinted() -> String { 50 | let drops = self.drops%1000000 51 | let xrp = self.drops/1000000 52 | let numberFormatter = NumberFormatter() 53 | numberFormatter.numberStyle = .decimal 54 | let formattedNumber = numberFormatter.string(from: NSNumber(value: xrp))! 55 | let leadingZeros: [Character] = Array(repeating: "0", count: 6 - String(drops).count) 56 | return formattedNumber + "." + String(leadingZeros) + String(drops) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Sources/XRPKit/SigningAlgorithms/ED25519 (pebble8888)/ed25519_utility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ed25519_utility.swift 3 | // 4 | // Copyright 2017 pebble8888. All rights reserved. 5 | // 6 | // This software is provided 'as-is', without any express or implied 7 | // warranty. In no event will the authors be held liable for any damages 8 | // arising from the use of this software. 9 | // 10 | // Permission is granted to anyone to use this software for any purpose, 11 | // including commercial applications, and to alter it and redistribute it 12 | // freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 19 | // 2. Altered source versions must be plainly marked as such, and must not be 20 | // misrepresented as being the original software. 21 | // 22 | // 3. This notice may not be removed or altered from any source distribution. 23 | // 24 | 25 | import Foundation 26 | #if NO_USE_CryptoSwift 27 | import CommonCrypto 28 | #else 29 | import CryptoSwift 30 | #endif 31 | 32 | extension String { 33 | func unhexlify() -> [UInt8] { 34 | var pos = startIndex 35 | return (0.. String { 44 | return self.map({ String(format: "%02x", $0) }).joined() 45 | } 46 | } 47 | 48 | func sha512(_ s: [UInt8]) -> [UInt8] { 49 | #if NO_USE_CryptoSwift 50 | let data = Data(s) 51 | var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH)) 52 | data.withUnsafeBytes { (p: UnsafeRawBufferPointer) -> Void in 53 | CC_SHA512(p.baseAddress, CC_LONG(data.count), &digest) 54 | } 55 | return digest 56 | #else 57 | return s.sha512() 58 | #endif 59 | } 60 | -------------------------------------------------------------------------------- /Sources/XRPKit/HDWallet/BigInt/VarInt.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VarInt.swift 3 | // HDWalletKit 4 | // 5 | // Created by Pavlo Boiko on 1/6/19. 6 | // Copyright © 2019 Essentia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal struct VarInt: ExpressibleByIntegerLiteral { 12 | internal typealias IntegerLiteralType = UInt64 13 | internal let underlyingValue: UInt64 14 | let length: UInt8 15 | let data: Data 16 | 17 | internal init(integerLiteral value: UInt64) { 18 | self.init(value) 19 | } 20 | 21 | /* 22 | 0xfc : 252 23 | 0xfd : 253 24 | 0xfe : 254 25 | 0xff : 255 26 | 27 | 0~252 : 1-byte(0x00 ~ 0xfc) 28 | 253 ~ 65535: 3-byte(0xfd00fd ~ 0xfdffff) 29 | 65536 ~ 4294967295 : 5-byte(0xfe010000 ~ 0xfeffffffff) 30 | 4294967296 ~ 1.84467441e19 : 9-byte(0xff0000000100000000 ~ 0xfeffffffffffffffff) 31 | */ 32 | internal init(_ value: UInt64) { 33 | underlyingValue = value 34 | 35 | switch value { 36 | case 0...252: 37 | length = 1 38 | data = Data() + Data([UInt8(value).littleEndian]) 39 | case 253...0xffff: 40 | length = 2 41 | data = Data() + UInt8(0xfd).littleEndian.data + UInt16(value).littleEndian.data 42 | case 0x10000...0xffffffff: 43 | length = 4 44 | data = Data() + UInt8(0xfe).littleEndian.data + UInt32(value).littleEndian.data 45 | case 0x100000000...0xffffffffffffffff: 46 | fallthrough 47 | default: 48 | length = 8 49 | data = Data() + UInt8(0xff).littleEndian.data + UInt64(value).littleEndian.data 50 | } 51 | } 52 | 53 | internal init(_ value: Int) { 54 | self.init(UInt64(value)) 55 | } 56 | 57 | internal func serialized() -> Data { 58 | return data 59 | } 60 | 61 | internal static func deserialize(_ data: Data) -> VarInt { 62 | return data.to(type: self) 63 | } 64 | } 65 | 66 | extension VarInt: CustomStringConvertible { 67 | internal var description: String { 68 | return "\(underlyingValue)" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AnyCodable", 6 | "repositoryURL": "https://github.com/Flight-School/AnyCodable.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "6e9610258e9f1044b5e0d88545052846448b43c3", 10 | "version": "0.2.3" 11 | } 12 | }, 13 | { 14 | "package": "BigInt", 15 | "repositoryURL": "https://github.com/Boilertalk/BigInt.swift.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "fd9b2ebff53e7fd211a7d2b2ea682db300a8571b", 19 | "version": "1.0.0" 20 | } 21 | }, 22 | { 23 | "package": "CryptoSwift", 24 | "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "a44caef0550c346e0ab9172f7c9a3852c1833599", 28 | "version": "1.3.0" 29 | } 30 | }, 31 | { 32 | "package": "secp256k1", 33 | "repositoryURL": "https://github.com/Boilertalk/secp256k1.swift.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "823281fe9def21b384099b72a9a53ca988317b20", 37 | "version": "0.1.4" 38 | } 39 | }, 40 | { 41 | "package": "swift-nio", 42 | "repositoryURL": "https://github.com/apple/swift-nio.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "4409b57d4c0c40d41ac2b320fccf02e4d451e3db", 46 | "version": "2.13.0" 47 | } 48 | }, 49 | { 50 | "package": "swift-nio-ssl", 51 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "cf54f5c1db1c3740a6c7d662dc8569c150c3846c", 55 | "version": "2.6.0" 56 | } 57 | }, 58 | { 59 | "package": "websocket-kit", 60 | "repositoryURL": "https://github.com/vapor/websocket-kit.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "766b4b0005a158550c345671c8e7cea42af104b6", 64 | "version": "2.0.0-beta.2.3" 65 | } 66 | } 67 | ] 68 | }, 69 | "version": 1 70 | } 71 | -------------------------------------------------------------------------------- /XRPKit.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint XRPKit.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'XRPKit' 11 | s.version = '0.3.0' 12 | s.summary = 'Swift SDK for interacting with XRP Ledger' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = 'XRPKit is a Swift SDK built for interacting with the XRP Ledger. XRPKit supports offline wallet creation, offline transaction creation/signing, and submitting transactions to the XRP ledger. XRPKit supports both the secp256k1 and ed25519 algorithms. XRPKit is available on iOS, macOS and Linux (SPM)' 21 | 22 | s.homepage = 'https://github.com/MitchLang009/XRPKit' 23 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 24 | s.license = { :type => 'MIT', :file => 'LICENSE' } 25 | s.author = { 'MitchLang009' => 'mitch.s.lang@gmail.com' } 26 | s.source = { :git => 'https://github.com/MitchLang009/XRPKit.git', :tag => s.version.to_s } 27 | # s.social_media_url = 'https://twitter.com/' 28 | 29 | s.ios.deployment_target = "12.0" 30 | s.osx.deployment_target = "10.10" 31 | s.tvos.deployment_target = '10.0' 32 | s.watchos.deployment_target = '3.0' 33 | s.swift_version = '5.0' 34 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' } 35 | 36 | s.source_files = 'Sources/XRPKit/**/*' 37 | # s.resources = 'XRPKit/Assets/*.xcassets' 38 | # s.resource_bundles = { 39 | # 'XRPKit' => ['XRPKit/Assets/*.png'] 40 | # } 41 | 42 | s.dependency 'secp256k1.swift' 43 | s.dependency 'CryptoSwift' 44 | s.dependency 'BigInt' 45 | s.dependency 'AnyCodable-FlightSchool' 46 | s.dependency 'SwiftNIO' 47 | 48 | s.static_framework = true 49 | 50 | end 51 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/IssuedAmount.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IssuedAmount.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/9/19. 6 | // 7 | 8 | import Foundation 9 | 10 | class IssuedAmount { 11 | 12 | let MIN_MANTISSA: Int64 = 1000000000000000 //10^15 13 | let MAX_MANTISSA: Int64 = 10000000000000000 - 1 //10^16-1 14 | let MIN_EXP = -96 15 | let MAX_EXP = 80 16 | 17 | var stringVal: String! 18 | var decimal: Decimal! 19 | 20 | init(value: String) { 21 | self.stringVal = value 22 | 23 | if let value = Decimal(string: stringVal) { 24 | self.decimal = value 25 | } else { 26 | fatalError() 27 | } 28 | } 29 | 30 | func canonicalize() -> Data { 31 | if self.decimal == 0 { 32 | return canonicalZeroSerial() 33 | } 34 | 35 | let sign = decimal.sign 36 | var exponent:Int = decimal.exponent 37 | let digits = stringVal.replacingOccurrences(of: ".", with: "") 38 | var mantissa:UInt64 = UInt64(digits)! 39 | 40 | while mantissa < MIN_MANTISSA && exponent > MIN_EXP { 41 | mantissa = mantissa * 10 42 | exponent = exponent - 1 43 | } 44 | 45 | while mantissa > MAX_MANTISSA { 46 | if exponent > MAX_EXP { 47 | fatalError() 48 | } 49 | mantissa = mantissa / 10 50 | exponent = exponent + 1 51 | } 52 | 53 | if exponent < MIN_EXP || mantissa < MIN_MANTISSA { 54 | return self.canonicalZeroSerial() 55 | } 56 | 57 | if exponent > MAX_EXP || mantissa > MAX_MANTISSA { 58 | fatalError() 59 | } 60 | 61 | var serial: UInt64 = 0x8000000000000000 // "Not XRP" bit set 62 | if sign == .plus { 63 | serial = serial | 0x4000000000000000 // "Is positive" bit set 64 | } 65 | serial |= (UInt64(exponent+97) << 54) // next 8 bits are exponent 66 | serial |= mantissa // last 54 bits are mantissa 67 | return serial.bigEndian.data 68 | } 69 | 70 | func canonicalZeroSerial() -> Data { 71 | /* 72 | Returns canonical format for zero (a special case): 73 | - "Not XRP" bit = 1 74 | - Everything else is zeroes 75 | - Arguably this means it's canonically written as "negative zero" 76 | because the encoding usually uses 1 for positive. 77 | */ 78 | var zero: [UInt8] = Array(repeating: 0, count: 8) 79 | zero[0] = 0x80 80 | return Data(zero) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPTransaction.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/10/19. 6 | // 7 | 8 | import Foundation 9 | import NIO 10 | 11 | public class XRPTransaction: XRPRawTransaction { 12 | 13 | var wallet: XRPWallet 14 | 15 | @available(*, unavailable) 16 | override init(fields: [String:Any]) { 17 | fatalError() 18 | } 19 | 20 | internal init(wallet: XRPWallet, fields: [String:Any]) { 21 | self.wallet = wallet 22 | var _fields = fields 23 | _fields["Account"] = wallet.address 24 | super.init(fields: _fields) 25 | } 26 | 27 | // autofills ledger sequence, fee, and sequence 28 | func autofill() -> EventLoopFuture { 29 | 30 | let promise = eventGroup.next().makePromise(of: XRPTransaction.self) 31 | 32 | // network calls to retrive current account and ledger info 33 | _ = XRPLedger.currentLedgerInfo().map { (ledgerInfo) in 34 | _ = XRPLedger.getAccountInfo(account: self.wallet.address).map { (accountInfo) in 35 | // dictionary containing transaction fields 36 | let filledFields: [String:Any] = [ 37 | "LastLedgerSequence" : ledgerInfo.index+5, 38 | "Fee" : "40", // FIXME: determine fee automatically 39 | "Sequence" : accountInfo.sequence, 40 | ] 41 | self.fields = self.fields.merging(self.enforceJSONTypes(fields: filledFields)) { (_, new) in new } 42 | promise.succeed(self) 43 | }.recover { (error) in 44 | promise.fail(error) 45 | } 46 | }.recover { (error) in 47 | promise.fail(error) 48 | } 49 | return promise.futureResult 50 | } 51 | 52 | public func send() -> EventLoopFuture { 53 | 54 | let promise = eventGroup.next().makePromise(of: NSDictionary.self) 55 | 56 | // autofill missing transaction fields (online) 57 | _ = self.autofill().map { (tx) in 58 | // sign the transaction (offline) 59 | let signedTransaction = try! tx.sign(wallet: tx.wallet) 60 | 61 | // submit the transaction (online) 62 | _ = signedTransaction.submit().map { (dict) in 63 | promise.succeed(dict) 64 | }.recover { (error) in 65 | promise.fail(error) 66 | } 67 | }.recover { (error) in 68 | promise.fail(error) 69 | } 70 | 71 | return promise.futureResult 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPAddress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPAddress.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/15/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum XRPAddressError: Error { 11 | case invalidAddress 12 | case checksumFails 13 | } 14 | 15 | public struct XRPAddress { 16 | var rAddress: String 17 | var tag: UInt32? 18 | var isTest: Bool 19 | var xAddress: String { 20 | return XRPAddress.encodeXAddress(rAddress: self.rAddress, tag: self.tag, test: self.isTest) 21 | } 22 | 23 | public init(rAddress: String, tag: UInt32? = nil, isTest: Bool = false) throws { 24 | if !XRPSeedWallet.validate(address: rAddress) { 25 | throw XRPAddressError.invalidAddress 26 | } 27 | self.rAddress = rAddress 28 | self.tag = tag 29 | self.isTest = false 30 | } 31 | 32 | public init(xAddress: String) throws { 33 | let data = Data(base58Decoding: xAddress)! 34 | let check = data.suffix(4).bytes 35 | let concatenated = data.prefix(31).bytes 36 | let tagBytes = concatenated[23...] 37 | let flags = concatenated[22] 38 | let prefix = concatenated[..<2] 39 | let accountID = concatenated[2..<22] 40 | let prefixedAccountID = Data([0x00]) + accountID 41 | let checksum = Data(prefixedAccountID).sha256().sha256().prefix(through: 3) 42 | let addrrssData = prefixedAccountID + checksum 43 | let address = String(base58Encoding: addrrssData) 44 | 45 | if check == [UInt8](Data(concatenated).sha256().sha256().prefix(through: 3)) { 46 | let data = Data(tagBytes) 47 | let _tag: UInt64 = data.withUnsafeBytes { $0.pointee } 48 | let tag: UInt32? = flags == 0x00 ? nil : UInt32(String(_tag))! 49 | 50 | if prefix == [0x05, 0x44] { // mainnet 51 | try self.init(rAddress: address, tag: tag) 52 | isTest = false 53 | } else if prefix == [0x04, 0x93] { // testnet 54 | try self.init(rAddress: address, tag: tag) 55 | isTest = true 56 | } else { 57 | throw XRPAddressError.invalidAddress 58 | } 59 | } else { 60 | throw XRPAddressError.checksumFails 61 | } 62 | } 63 | 64 | public static func decodeXAddress(xAddress: String) throws -> XRPAddress { 65 | return try self.init(xAddress: xAddress) 66 | } 67 | 68 | public static func encodeXAddress(rAddress: String, tag: UInt32? = nil, test: Bool = false ) -> String { 69 | let accountID = XRPSeedWallet.accountID(for: rAddress) 70 | let prefix: [UInt8] = test ? [0x04, 0x93] : [0x05, 0x44] 71 | let flags: [UInt8] = tag == nil ? [0x00] : [0x01] 72 | let tag = tag == nil ? [UInt8](UInt64(0).data) : [UInt8](UInt64(tag!).data) 73 | let concatenated = prefix + accountID + flags + tag 74 | let check = [UInt8](Data(concatenated).sha256().sha256().prefix(through: 3)) 75 | let concatenatedCheck: [UInt8] = concatenated + check 76 | return String(base58Encoding: Data(concatenatedCheck), alphabet: Base58String.xrpAlphabet) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/XRPKit/HDWallet/Coin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coin.swift 3 | // HDWalletKit 4 | // 5 | // Created by Pavlo Boiko on 10/3/18. 6 | // Copyright © 2018 Essentia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal enum Coin { 12 | case bitcoin 13 | case ethereum 14 | case litecoin 15 | case bitcoinCash 16 | case dash 17 | 18 | //https://github.com/satoshilabs/slips/blob/master/slip-0132.md 19 | internal var privateKeyVersion: UInt32 { 20 | switch self { 21 | case .litecoin: 22 | return 0x019D9CFE 23 | case .bitcoinCash: fallthrough 24 | case .bitcoin: 25 | return 0x0488ADE4 26 | case .dash: 27 | return 0x02FE52CC 28 | default: 29 | fatalError("Not implemented") 30 | } 31 | } 32 | // P2PKH 33 | internal var publicKeyHash: UInt8 { 34 | switch self { 35 | case .litecoin: 36 | return 0x30 37 | case .bitcoinCash: fallthrough 38 | case .bitcoin: 39 | return 0x00 40 | case .dash: 41 | return 0x4C 42 | default: 43 | fatalError("Not implemented") 44 | } 45 | } 46 | 47 | // P2SH 48 | internal var scriptHash: UInt8 { 49 | switch self { 50 | case .bitcoinCash: fallthrough 51 | case .litecoin: fallthrough 52 | case .bitcoin: 53 | return 0x05 54 | case .dash: 55 | return 0x10 56 | default: 57 | fatalError("Not implemented") 58 | } 59 | } 60 | 61 | //https://www.reddit.com/r/litecoin/comments/6vc8tc/how_do_i_convert_a_raw_private_key_to_wif_for/ 62 | internal var wifAddressPrefix: UInt8 { 63 | switch self { 64 | case .bitcoinCash: fallthrough 65 | case .bitcoin: 66 | return 0x80 67 | case .litecoin: 68 | return 0xB0 69 | case .dash: 70 | return 0xCC 71 | default: 72 | fatalError("Not implemented") 73 | } 74 | } 75 | 76 | internal var addressPrefix:String { 77 | switch self { 78 | case .ethereum: 79 | return "0x" 80 | default: 81 | return "" 82 | } 83 | } 84 | 85 | internal var uncompressedPkSuffix: UInt8 { 86 | return 0x01 87 | } 88 | 89 | 90 | internal var coinType: UInt32 { 91 | switch self { 92 | case .bitcoin: 93 | return 0 94 | case .litecoin: 95 | return 2 96 | case .dash: 97 | return 5 98 | case .ethereum: 99 | return 60 100 | case .bitcoinCash: 101 | return 145 102 | } 103 | } 104 | 105 | internal var scheme: String { 106 | switch self { 107 | case .bitcoin: 108 | return "bitcoin" 109 | case .litecoin: 110 | return "litecoin" 111 | case .bitcoinCash: 112 | return "bitcoincash" 113 | case .dash: 114 | return "dash" 115 | default: return "" 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/Bip39Mnemonic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mnemonic.swift 3 | // WalletKit 4 | // 5 | // Created by yuzushioh on 2018/02/11. 6 | // Copyright © 2018 yuzushioh. All rights reserved. 7 | // 8 | import Foundation 9 | import CryptoSwift 10 | 11 | // https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki 12 | public final class Bip39Mnemonic { 13 | public enum Strength: Int { 14 | case normal = 128 15 | case hight = 256 16 | } 17 | 18 | public static func create(strength: Strength = .normal, language: WordList = .english) throws -> String { 19 | let byteCount = strength.rawValue / 8 20 | let bytes = try Data.randomBytes(length: byteCount) 21 | return create(entropy: bytes, language: language) 22 | } 23 | 24 | public static func create(entropy: Data, language: WordList = .english) -> String { 25 | let entropybits = String(entropy.flatMap { ("00000000" + String($0, radix: 2)).suffix(8) }) 26 | let hashBits = String(entropy.sha256().flatMap { ("00000000" + String($0, radix: 2)).suffix(8) }) 27 | let checkSum = String(hashBits.prefix((entropy.count * 8) / 32)) 28 | 29 | let words = language.words 30 | let concatenatedBits = entropybits + checkSum 31 | 32 | var mnemonic: [String] = [] 33 | for index in 0..<(concatenatedBits.count / 11) { 34 | let startIndex = concatenatedBits.index(concatenatedBits.startIndex, offsetBy: index * 11) 35 | let endIndex = concatenatedBits.index(startIndex, offsetBy: 11) 36 | let wordIndex = Int(strtoul(String(concatenatedBits[startIndex.. Data { 44 | guard let password = mnemonic.decomposedStringWithCompatibilityMapping.data(using: .utf8) else { 45 | fatalError("Nomalizing password failed in \(self)") 46 | } 47 | 48 | guard let salt = ("mnemonic" + passphrase).decomposedStringWithCompatibilityMapping.data(using: .utf8) else { 49 | fatalError("Nomalizing salt failed in \(self)") 50 | } 51 | 52 | return PBKDF2SHA512(password: password.bytes, salt: salt.bytes) 53 | } 54 | } 55 | 56 | public func PBKDF2SHA512(password: [UInt8], salt: [UInt8]) -> Data { 57 | let output: [UInt8] 58 | do { 59 | output = try PKCS5.PBKDF2(password: password, salt: salt, iterations: 2048, variant: .sha512).calculate() 60 | } catch let error { 61 | fatalError("PKCS5.PBKDF2 faild: \(error.localizedDescription)") 62 | } 63 | return Data(output) 64 | } 65 | extension Data { 66 | static func randomBytes(length: Int) throws -> Data { 67 | return try Data(URandom().bytes(count: length)) 68 | } 69 | } 70 | 71 | extension String { 72 | func dropString(_ from: Int, length: Int) -> String { 73 | if from < 0 || length + from > self.count { return "" } 74 | var result = "" 75 | for (idx, char) in self.enumerated() { if idx >= from && idx < length + from { result.append(char) } } 76 | return result 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/XRPKit/SigningAlgorithms/ED25519 (pebble8888)/ed25519_verify.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ed25519_verify.swift 3 | // 4 | // Copyright 2017 pebble8888. All rights reserved. 5 | // 6 | // This software is provided 'as-is', without any express or implied 7 | // warranty. In no event will the authors be held liable for any damages 8 | // arising from the use of this software. 9 | // 10 | // Permission is granted to anyone to use this software for any purpose, 11 | // including commercial applications, and to alter it and redistribute it 12 | // freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 19 | // 2. Altered source versions must be plainly marked as such, and must not be 20 | // misrepresented as being the original software. 21 | // 22 | // 3. This notice may not be removed or altered from any source distribution. 23 | // 24 | 25 | import Foundation 26 | 27 | public extension Ed25519 { 28 | /// verify 29 | /// - Parameters: 30 | /// - signature: signature 64bytes (R 32byte + S 32byte) 31 | /// - message: message 32 | /// - publicKey: public key 32bytes 33 | static func verify(signature: [UInt8], message: [UInt8], publicKey: [UInt8]) -> Bool { 34 | if publicKey.count != 32 { 35 | return false 36 | } 37 | if signature.count != 64 { 38 | return false 39 | } 40 | let smlen = 64 + message.count 41 | var sm = [UInt8](repeating: 0, count: smlen) 42 | var rcopy = [UInt8](repeating: 0, count: 32) // point R 43 | var k = [UInt8](repeating: 0, count: 64) 44 | var rcheck = [UInt8](repeating: 0, count: 32) 45 | var ge_a = ge() // unpacked public info from pk argument 46 | var ge_b = ge() 47 | var sc_k = sc() // integer k 48 | var sc_s = sc() 49 | 50 | // rapid check whether S is smaller than group order 51 | if signature[63] & UInt8(224) != 0 { 52 | return false 53 | } 54 | // exact check whether S is smaller than group order 55 | if !sc.sc25519_less_order(Array(signature[32..<64])) { 56 | return false 57 | } 58 | if !ge.ge25519_unpackneg_vartime(&ge_a, publicKey) { 59 | return false 60 | } 61 | 62 | // point R 63 | for i in 0..<32 { 64 | rcopy[i] = signature[i] 65 | } 66 | 67 | // integer S 68 | sc.sc25519_from32bytes(&sc_s, Array(signature[32..<64])) 69 | 70 | // signature 64 bytes(R 32byte + S 32byte) 71 | for i in 0..<32 { 72 | sm[i] = signature[i] 73 | } 74 | // R 32byte + A 32byte 75 | for i in 0..<32 { 76 | sm[32+i] = publicKey[i] 77 | } 78 | // R 32byte + A 32byte + message 79 | for i in 0.. Bool { 41 | return lhs.int32 == rhs.int32 42 | } 43 | 44 | internal static func < (lhs: BigNumber, rhs: BigNumber) -> Bool { 45 | return lhs.int32 < rhs.int32 46 | } 47 | } 48 | 49 | private extension Int32 { 50 | func toBigNum() -> Data { 51 | let isNegative: Bool = self < 0 52 | var value: UInt32 = isNegative ? UInt32(-self) : UInt32(self) 53 | 54 | var data = Data(bytes: &value, count: MemoryLayout.size(ofValue: value)) 55 | while data.last == 0 { 56 | data.removeLast() 57 | } 58 | 59 | var bytes: [UInt8] = [] 60 | for d in data.reversed() { 61 | if bytes.isEmpty && d >= 0x80 { 62 | bytes.append(0) 63 | } 64 | bytes.append(d) 65 | } 66 | 67 | if isNegative { 68 | let first = bytes.removeFirst() 69 | bytes.insert(first + 0x80, at: 0) 70 | } 71 | 72 | let bignum = Data(bytes.reversed()) 73 | return bignum 74 | 75 | } 76 | } 77 | 78 | private extension Data { 79 | func toInt32() -> Int32 { 80 | guard !self.isEmpty else { 81 | return 0 82 | } 83 | var data = self 84 | var bytes: [UInt8] = [] 85 | var last = data.removeLast() 86 | let isNegative: Bool = last >= 0x80 87 | 88 | while !data.isEmpty { 89 | bytes.append(data.removeFirst()) 90 | } 91 | 92 | if isNegative { 93 | last -= 0x80 94 | } 95 | bytes.append(last) 96 | 97 | let value: Int32 = Data(bytes).to(type: Int32.self) 98 | return isNegative ? -value: value 99 | } 100 | } 101 | 102 | extension Data { 103 | init(from value: T) { 104 | var value = value 105 | self.init(buffer: UnsafeBufferPointer(start: &value, count: 1)) 106 | } 107 | 108 | func to(type: T.Type) -> T { 109 | return self.withUnsafeBytes { (ptr) -> T in 110 | return ptr.baseAddress!.assumingMemoryBound(to: T.self).pointee 111 | } 112 | } 113 | 114 | func to(type: String.Type) -> String { 115 | return String(bytes: self, encoding: .ascii)!.replacingOccurrences(of: "\0", with: "") 116 | } 117 | 118 | func to(type: VarInt.Type) -> VarInt { 119 | let value: UInt64 120 | let length = self[0..<1].to(type: UInt8.self) 121 | switch length { 122 | case 0...252: 123 | value = UInt64(length) 124 | case 0xfd: 125 | value = UInt64(self[1...2].to(type: UInt16.self)) 126 | case 0xfe: 127 | value = UInt64(self[1...4].to(type: UInt32.self)) 128 | case 0xff: 129 | fallthrough 130 | default: 131 | value = self[1...8].to(type: UInt64.self) 132 | } 133 | return VarInt(value) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/XRPKit/HDWallet/PrivateKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrivateKey.swift 3 | // HDWalletKit 4 | // 5 | // Created by Pavlo Boiko on 10/4/18. 6 | // Copyright © 2018 Essentia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CryptoSwift 11 | import secp256k1 12 | #if os(Linux) 13 | import Glibc 14 | #endif 15 | 16 | 17 | enum PrivateKeyType { 18 | case hd 19 | case nonHd 20 | } 21 | 22 | internal struct PrivateKey { 23 | internal let raw: Data 24 | internal let chainCode: Data 25 | internal let index: UInt32 26 | internal let coin: Coin 27 | private var keyType: PrivateKeyType 28 | 29 | internal init(seed: Data, coin: Coin) { 30 | let output = try! Data(CryptoSwift.HMAC(key: "Bitcoin seed".data(using: .ascii)!.bytes, variant: .sha512).authenticate(seed.bytes)) 31 | self.raw = output[0..<32] 32 | self.chainCode = output[32..<64] 33 | self.index = 0 34 | self.coin = coin 35 | self.keyType = .hd 36 | } 37 | 38 | private init(privateKey: Data, chainCode: Data, index: UInt32, coin: Coin) { 39 | self.raw = privateKey 40 | self.chainCode = chainCode 41 | self.index = index 42 | self.coin = coin 43 | self.keyType = .hd 44 | } 45 | 46 | internal var publicKey: Data { 47 | var _data = raw 48 | let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN))! 49 | let data = try! Data(SECP256K1.derivePublicKey(ctx: ctx, secretKey: _data.getPointer()).compressed) 50 | secp256k1_context_destroy(ctx) 51 | return data 52 | } 53 | 54 | internal func wifCompressed() -> String { 55 | var data = Data() 56 | data += Data([coin.wifAddressPrefix]) 57 | data += raw 58 | data += Data([UInt8(0x01)]) 59 | data += data.sha256().sha256().prefix(4) 60 | return String(base58Encoding: data, alphabet: Base58String.btcAlphabet) 61 | } 62 | 63 | internal func get() -> String { 64 | switch self.coin { 65 | case .bitcoin: fallthrough 66 | case .litecoin: fallthrough 67 | case .dash: fallthrough 68 | case .bitcoinCash: 69 | return self.wifCompressed() 70 | case .ethereum: 71 | return self.raw.toHexString() 72 | } 73 | } 74 | 75 | internal func derived(at node:DerivationNode) -> PrivateKey { 76 | guard keyType == .hd else { fatalError() } 77 | let edge: UInt32 = 0x80000000 78 | guard (edge & node.index) == 0 else { fatalError("Invalid child index") } 79 | 80 | var data = Data() 81 | switch node { 82 | case .hardened: 83 | data += Data([UInt8(0)]) 84 | data += raw 85 | case .notHardened: 86 | var _data = raw 87 | let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN))! 88 | data += try! Data(SECP256K1.derivePublicKey(ctx: ctx, secretKey: _data.getPointer()).compressed) 89 | secp256k1_context_destroy(ctx) 90 | } 91 | 92 | #if os(Linux) 93 | let derivingIndex = Glibc.ntohl(node.hardens ? (edge | node.index) : node.index) 94 | #else 95 | let derivingIndex = CFSwapInt32BigToHost(node.hardens ? (edge | node.index) : node.index) 96 | #endif 97 | data += derivingIndex.data 98 | 99 | let digest = try! Data(HMAC.init(key: chainCode.bytes, variant: .sha512).authenticate(data.bytes)) 100 | let factor = BInt(data: digest[0..<32]) 101 | 102 | let curveOrder = BInt(hex: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141")! 103 | let derivedPrivateKey = ((BInt(data: raw) + factor) % curveOrder).data 104 | let derivedChainCode = digest[32..<64] 105 | return PrivateKey( 106 | privateKey: derivedPrivateKey, 107 | chainCode: derivedChainCode, 108 | index: derivingIndex, 109 | coin: coin 110 | ) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/Transactions/XRPRawTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XRPRawTransaction.swift 3 | // AnyCodable 4 | // 5 | // Created by Mitch Lang on 2/4/20. 6 | // 7 | 8 | import Foundation 9 | import NIO 10 | import BigInt 11 | 12 | let HASH_TX_SIGN: [UInt8] = [0x53,0x54,0x58, 0x00] 13 | let HASH_TX_MULTISIGN: [UInt8] = [0x53,0x4D,0x54,0x00] 14 | 15 | public class XRPRawTransaction { 16 | 17 | public internal(set) var fields: [String: Any] = [:] 18 | 19 | public init(fields: [String:Any]) { 20 | self.fields = enforceJSONTypes(fields: fields) 21 | } 22 | 23 | public func sign(wallet: XRPWallet) throws -> XRPRawTransaction { 24 | 25 | // make sure all fields are compatible 26 | self.fields = self.enforceJSONTypes(fields: self.fields) 27 | 28 | // add account public key to fields 29 | self.fields["SigningPubKey"] = wallet.publicKey as AnyObject 30 | 31 | // serialize transation to binary 32 | let blob = Serializer().serializeTx(tx: self.fields, forSigning: true) 33 | 34 | // add the transaction prefix to the blob 35 | let data: [UInt8] = HASH_TX_SIGN + blob 36 | 37 | // sign the prefixed blob 38 | let algorithm = XRPSeedWallet.getSeedTypeFrom(publicKey: wallet.publicKey).algorithm 39 | let signature = try algorithm.sign(message: data, privateKey: [UInt8](Data(hex: wallet.privateKey))) 40 | 41 | // verify signature 42 | let verified = try algorithm.verify(signature: signature, message: data, publicKey: [UInt8](Data(hex: wallet.publicKey))) 43 | if !verified { 44 | fatalError() 45 | } 46 | 47 | // add the signature to the fields 48 | self.fields["TxnSignature"] = Data(signature).toHexString().uppercased() as Any 49 | return self 50 | } 51 | 52 | public func addMultiSignSignature(wallet: XRPWallet) throws -> XRPRawTransaction { 53 | // make sure all fields are compatible 54 | self.fields = self.enforceJSONTypes(fields: self.fields) 55 | 56 | // add account public key to fields 57 | self.fields["SigningPubKey"] = "" 58 | 59 | // serialize transation to binary 60 | let blob = Serializer().serializeTx(tx: self.fields, forSigning: true) 61 | 62 | // add the transaction prefix/suffix to the blob 63 | let data: [UInt8] = HASH_TX_MULTISIGN + blob + wallet.accountID 64 | 65 | // sign the prefixed blob 66 | let algorithm = XRPSeedWallet.getSeedTypeFrom(publicKey: wallet.publicKey).algorithm 67 | let signature = try algorithm.sign(message: data, privateKey: [UInt8](Data(hex: wallet.privateKey))) 68 | 69 | // verify signature 70 | let verified = try algorithm.verify(signature: signature, message: data, publicKey: [UInt8](Data(hex: wallet.publicKey))) 71 | if !verified { 72 | fatalError() 73 | } 74 | 75 | // add the signature to the fields 76 | let signatureDictionary = NSDictionary(dictionary: [ 77 | "Signer" : NSDictionary(dictionary: [ 78 | "Account" : wallet.address, 79 | "SigningPubKey" : wallet.publicKey, 80 | "TxnSignature" : signature.toHexString().uppercased() as Any, 81 | ]) 82 | ]) 83 | var signers: [NSDictionary] = self.fields["Signers"] as? [NSDictionary] ?? [NSDictionary]() 84 | signers.append(signatureDictionary) 85 | signers.sort { (d1, d2) in 86 | let n1 = Data(base58Decoding: (d1["Signer"] as! NSDictionary)["Account"] as! String)! 87 | let n2 = Data(base58Decoding: (d2["Signer"] as! NSDictionary)["Account"] as! String)! 88 | return BigInt(n1.hexadecimal, radix: 16)! < BigInt(n2.hexadecimal, radix: 16)! 89 | } 90 | self.fields["Signers"] = signers as Any 91 | 92 | 93 | return self 94 | } 95 | 96 | public func submit() -> EventLoopFuture { 97 | let promise = eventGroup.next().makePromise(of: NSDictionary.self) 98 | let tx = Serializer().serializeTx(tx: self.fields, forSigning: false).toHexString().uppercased() 99 | _ = XRPLedger.submit(txBlob: tx).map { (tx) in 100 | promise.succeed(tx) 101 | }.recover { (error) in 102 | promise.fail(error) 103 | } 104 | return promise.futureResult 105 | } 106 | 107 | public func getJSONString() -> String { 108 | let jsonData = try! JSONSerialization.data(withJSONObject: self.fields, options: .prettyPrinted) 109 | return String(data: jsonData, encoding: .utf8)! 110 | } 111 | 112 | internal func enforceJSONTypes(fields: [String:Any]) -> [String:Any]{ 113 | let jsonData = try! JSONSerialization.data(withJSONObject: fields, options: .prettyPrinted) 114 | let fields = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableLeaves) 115 | return fields as! [String:Any] 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/10/19. 6 | // 7 | 8 | import Foundation 9 | import CryptoSwift 10 | import BigInt 11 | 12 | func sha512HalfHash(data: [UInt8]) -> [UInt8] { 13 | return [UInt8](Data(data).sha512().prefix(through: 31)) 14 | } 15 | 16 | extension Data { 17 | mutating func getPointer() -> UnsafeMutablePointer { 18 | return self.withUnsafeMutableBytes { (bytePtr) in 19 | bytePtr.bindMemory(to: UInt8.self).baseAddress! 20 | } 21 | } 22 | 23 | func sha512Half() -> Data { 24 | Data(self.sha512().prefix(through: 31)) 25 | } 26 | } 27 | 28 | extension Data { 29 | 30 | init(hex: String) { 31 | var data = Data(capacity: hex.count / 2) 32 | 33 | let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive) 34 | regex.enumerateMatches(in: hex, options: [], range: NSMakeRange(0, hex.count)) { match, flags, stop in 35 | let byteString = (hex as NSString).substring(with: match!.range) 36 | var num = UInt8(byteString, radix: 16)! 37 | data.append(&num, count: 1) 38 | } 39 | 40 | self = data 41 | } 42 | } 43 | 44 | extension String { 45 | var isNumber: Bool { 46 | return !isEmpty && rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil 47 | } 48 | } 49 | 50 | typealias Byte = UInt8 51 | enum Bit: Int { 52 | case zero, one 53 | } 54 | 55 | //extension Data { 56 | // var bytes: [Byte] { 57 | // var byteArray = [UInt8](repeating: 0, count: self.count) 58 | // self.copyBytes(to: &byteArray, count: self.count) 59 | // return byteArray 60 | // } 61 | //} 62 | 63 | extension Byte { 64 | var bits: [Bit] { 65 | let bitsOfAbyte = 8 66 | var bitsArray = [Bit](repeating: Bit.zero, count: bitsOfAbyte) 67 | for (index, _) in bitsArray.enumerated() { 68 | // Bitwise shift to clear unrelevant bits 69 | let bitVal: UInt8 = 1 << UInt8(bitsOfAbyte - 1 - index) 70 | let check = self & bitVal 71 | 72 | if check != 0 { 73 | bitsArray[index] = Bit.one 74 | } 75 | } 76 | return bitsArray 77 | } 78 | } 79 | 80 | extension String { 81 | 82 | /// Create `Data` from hexadecimal string representation 83 | /// 84 | /// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed. 85 | /// 86 | /// - returns: Data represented by this hexadecimal string. 87 | 88 | var hexadecimal: Data? { 89 | var data = Data(capacity: self.count / 2) 90 | 91 | let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive) 92 | regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in 93 | let byteString = (self as NSString).substring(with: match!.range) 94 | let num = UInt8(byteString, radix: 16)! 95 | data.append(num) 96 | } 97 | 98 | guard data.count > 0 else { return nil } 99 | 100 | return data 101 | } 102 | 103 | } 104 | 105 | extension Data { 106 | 107 | /// Hexadecimal string representation of `Data` object. 108 | 109 | var hexadecimal: String { 110 | return map { String(format: "%02x", $0) } 111 | .joined() 112 | } 113 | } 114 | 115 | extension Numeric { 116 | var data: Data { 117 | var source = self 118 | return Data(bytes: &source, count: MemoryLayout.size) 119 | } 120 | } 121 | 122 | public extension URL { 123 | static let xrpl_rpc_MainNetS1 = URL(string: "https://s1.ripple.com:51234/")! 124 | static let xrpl_rpc_MainNetS2 = URL(string: "https://s2.ripple.com:51234/")! 125 | static let xrpl_rpc_Testnet = URL(string: "https://s.altnet.rippletest.net:51234/")! 126 | static let xrpl_rpc_Devnet = URL(string: "https://s.devnet.rippletest.net:51234/")! 127 | static let xrpl_ws_MainnetS1 = URL(string: "wss://s1.ripple.com/")! 128 | static let xrpl_ws_MainnetS2 = URL(string: "wss://s2.ripple.com/")! 129 | static let xrpl_ws_Testnet = URL(string: "wss://s.altnet.rippletest.net/")! 130 | static let xrpl_ws_Devnet = URL(string: "wss://s.devnet.rippletest.net/")! 131 | } 132 | 133 | public enum XRPLHost: String { 134 | case xrpl_rpc_MainNetS1 = "s1.ripple.com:51234" 135 | case xrpl_rpc_MainNetS2 = "s2.ripple.com:51234" 136 | case xrpl_rpc_Testnet = "s.altnet.rippletest.net:51234" 137 | case xrpl_rpc_Devnet = "s.devnet.rippletest.net:51234" 138 | case xrpl_ws_MainnetS1 = "s1.ripple.com" 139 | case xrpl_ws_MainnetS2 = "s2.ripple.com" 140 | case xrpl_ws_Testnet = "s.altnet.rippletest.net" 141 | case xrpl_ws_Devnet = "s.devnet.rippletest.net" 142 | } 143 | 144 | extension Date { 145 | var timeIntervalSinceRippleEpoch: UInt64 { 146 | return UInt64(self.timeIntervalSince1970) - UInt64(946684800) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/XRPKit/Network/WebSocket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Mitch Lang on 1/31/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol XRPWebSocketDelegate { 11 | func onConnected(connection: XRPWebSocket) 12 | func onDisconnected(connection: XRPWebSocket, error: Error?) 13 | func onError(connection: XRPWebSocket, error: Error) 14 | func onResponse(connection: XRPWebSocket, response: XRPWebSocketResponse) 15 | func onStream(connection: XRPWebSocket, object: NSDictionary) 16 | } 17 | 18 | public protocol XRPWebSocket { 19 | func send(text: String) 20 | func send(data: Data) 21 | func connect(host: String) 22 | func disconnect() 23 | var delegate: XRPWebSocketDelegate? { 24 | get 25 | set 26 | } 27 | 28 | // convenience methods 29 | func subscribe(account: String) 30 | } 31 | 32 | class _WebSocket: NSObject { 33 | var delegate: XRPWebSocketDelegate? 34 | internal override init() {} 35 | fileprivate func handleResponse(connection: XRPWebSocket, data: Data) { 36 | if let response = try? JSONDecoder().decode(XRPWebSocketResponse.self, from: data) { 37 | self.delegate?.onResponse(connection: connection, response: response) 38 | } else if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary { 39 | self.delegate?.onStream(connection: connection, object: json) 40 | } 41 | 42 | } 43 | } 44 | 45 | #if canImport(WebSocketKit) 46 | 47 | import WebSocketKit 48 | 49 | class LinuxWebSocket: _WebSocket, XRPWebSocket { 50 | 51 | var ws: WebSocket! 52 | 53 | func send(text: String) { 54 | if ws != nil && !ws.isClosed { 55 | ws.send(text) 56 | } 57 | } 58 | 59 | func send(data: Data) { 60 | if ws != nil && !ws.isClosed { 61 | ws.send([UInt8](data)) 62 | } 63 | } 64 | 65 | func connect(host: String) { 66 | let client = WebSocketClient(eventLoopGroupProvider: .shared(eventGroup)) 67 | try! client.connect(scheme: "wss", host: host, port: 443, onUpgrade: { (_ws) -> () in 68 | self.ws = _ws 69 | }).wait() 70 | self.delegate?.onConnected(connection: self) 71 | self.ws.onText { (ws, text) in 72 | let data = text.data(using: .utf8)! 73 | self.handleResponse(connection: self, data: data) 74 | } 75 | self.ws.onBinary { (ws, byteBuffer) in 76 | fatalError() 77 | } 78 | _ = self.ws.onClose.map { 79 | self.delegate?.onDisconnected(connection: self, error: nil) 80 | } 81 | 82 | } 83 | 84 | func disconnect() { 85 | _ = ws.close() 86 | } 87 | 88 | 89 | 90 | } 91 | 92 | #elseif !os(Linux) 93 | 94 | 95 | import Foundation 96 | 97 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 98 | class AppleWebSocket: _WebSocket, XRPWebSocket, URLSessionWebSocketDelegate { 99 | 100 | var webSocketTask: URLSessionWebSocketTask! 101 | var urlSession: URLSession! 102 | let delegateQueue = OperationQueue() 103 | var connected: Bool = false 104 | 105 | func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) { 106 | self.connected = true 107 | self.delegate?.onConnected(connection: self) 108 | } 109 | 110 | func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { 111 | self.delegate?.onDisconnected(connection: self, error: nil) 112 | self.connected = false 113 | } 114 | 115 | func connect(host: String) { 116 | let url = URL(string: "wss://" + host + "/")! 117 | urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: delegateQueue) 118 | webSocketTask = urlSession.webSocketTask(with: url) 119 | webSocketTask.resume() 120 | self.connected = true 121 | listen() 122 | } 123 | 124 | func disconnect() { 125 | webSocketTask.cancel(with: .goingAway, reason: nil) 126 | } 127 | 128 | func listen() { 129 | webSocketTask.receive { result in 130 | switch result { 131 | case .failure(let error): 132 | self.delegate?.onError(connection: self, error: error) 133 | case .success(let message): 134 | switch message { 135 | case .string(let text): 136 | let data = text.data(using: .utf8)! 137 | self.handleResponse(connection: self, data: data) 138 | case .data(let data): 139 | self.handleResponse(connection: self, data: data) 140 | @unknown default: 141 | fatalError() 142 | } 143 | 144 | self.listen() 145 | } 146 | } 147 | } 148 | 149 | func send(text: String) { 150 | if self.connected { 151 | webSocketTask.send(URLSessionWebSocketTask.Message.string(text)) { error in 152 | if let error = error { 153 | self.delegate?.onError(connection: self, error: error) 154 | } 155 | } 156 | } 157 | } 158 | 159 | func send(data: Data) { 160 | if self.connected { 161 | webSocketTask.send(URLSessionWebSocketTask.Message.data(data)) { error in 162 | if let error = error { 163 | self.delegate?.onError(connection: self, error: error) 164 | } 165 | } 166 | } 167 | } 168 | } 169 | 170 | #endif 171 | 172 | -------------------------------------------------------------------------------- /Sources/XRPKit/SigningAlgorithms/ED25519 (pebble8888)/ed25519_sign.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ed25519_sign.swift 3 | // 4 | // Copyright 2017 pebble8888. All rights reserved. 5 | // 6 | // This software is provided 'as-is', without any express or implied 7 | // warranty. In no event will the authors be held liable for any damages 8 | // arising from the use of this software. 9 | // 10 | // Permission is granted to anyone to use this software for any purpose, 11 | // including commercial applications, and to alter it and redistribute it 12 | // freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 19 | // 2. Altered source versions must be plainly marked as such, and must not be 20 | // misrepresented as being the original software. 21 | // 22 | // 3. This notice may not be removed or altered from any source distribution. 23 | // 24 | 25 | import Foundation 26 | 27 | /** 28 | * ed25519 fast calculation implementation 29 | * ported from SUPERCOP https://bench.cr.yp.to/supercop.html 30 | */ 31 | public struct Ed25519 { 32 | 33 | static func crypto_hash_sha512(_ r: inout [UInt8], _ k: [UInt8], len: Int) { 34 | r = sha512(Array(k[0.. Bool { 46 | if x.count != 32 || y.count != 32 { 47 | return false 48 | } 49 | for i in 0..<32 { 50 | if x[i] != y[i] { 51 | return false 52 | } 53 | } 54 | return true 55 | } 56 | 57 | /// create keypair 58 | /// - Parameters: 59 | /// - publicKey: private key 32bytes 60 | /// - secretKey: secret key 32bytes 61 | public static func generateKeyPair() -> (publicKey: [UInt8], secretKey: [UInt8]) { 62 | var secretKey = [UInt8](repeating: 0, count: 32) 63 | // create secret key 32byte 64 | randombytes(&secretKey, len: 32) 65 | let publicKey = calcPublicKey(secretKey: secretKey) 66 | return (publicKey, secretKey) 67 | } 68 | 69 | /// calc public key from secret key 70 | /// - Parameters: 71 | /// - secretKey: secret key 32bytes 72 | /// - Return: public key 32bytes 73 | public static func calcPublicKey(secretKey: [UInt8]) -> [UInt8] { 74 | assert(secretKey.count == 32) 75 | var sc_sk = sc() 76 | var ge_pk = ge() 77 | var az = [UInt8](repeating: 0, count: 64) 78 | var pk = [UInt8](repeating: 0, count: 32) 79 | // sha512 of sk 80 | crypto_hash_sha512(&az, secretKey, len: 32) 81 | // calc public key 82 | az[0] &= 248 // clear lowest 3bit 83 | az[31] &= 127 // clear highest bit 84 | az[31] |= 64 // set second highest bit 85 | 86 | sc.sc25519_from32bytes(&sc_sk, az) 87 | 88 | // gepk = a * G 89 | ge.ge25519_scalarmult_base(&ge_pk, sc_sk) 90 | ge.ge25519_pack(&pk, ge_pk) 91 | assert(pk.count == 32) 92 | return pk 93 | } 94 | 95 | /// validate key pair 96 | /// - Parameters: 97 | /// - publicKey: public key 32bytes 98 | /// - secretKey: secret key 32bytes 99 | public static func isValidKeyPair(publicKey: [UInt8], secretKey: [UInt8]) -> Bool { 100 | if publicKey.count != 32 { 101 | return false 102 | } 103 | if secretKey.count != 32 { 104 | return false 105 | } 106 | let calc_pk = calcPublicKey(secretKey: secretKey) 107 | for i in 0..<32 { 108 | if calc_pk[i] != publicKey[i] { 109 | return false 110 | } 111 | } 112 | return true 113 | } 114 | 115 | /// signing 116 | /// - Parameters: 117 | /// - message: message 118 | /// - secretKey: 32 bytes secret key 119 | /// - Return: 64 bytes signature 120 | public static func sign(message: [UInt8], secretKey: [UInt8]) -> [UInt8] { 121 | assert(secretKey.count == 32) 122 | let mlen: Int = message.count 123 | var az = [UInt8](repeating: 0, count: 64) 124 | var nonce = [UInt8](repeating: 0, count: 64) 125 | var hram = [UInt8](repeating: 0, count: 64) 126 | var sc_k = sc() 127 | var sc_s = sc() 128 | var sc_sk = sc() 129 | var ge_r = ge() 130 | /* pk: 32-byte public key A */ 131 | let pk = calcPublicKey(secretKey: secretKey) 132 | crypto_hash_sha512(&az, secretKey, len: 32) 133 | az[0] &= 248 // clear lowest 3bit 134 | az[31] &= 127 // clear highest bit 135 | az[31] |= 64 // set second highest bit 136 | 137 | var sm = [UInt8](repeating: 0, count: mlen+64) 138 | for i in 0.. XRPKeyPair { 31 | 32 | // FIXME: NOT THE FULL DERIVATION PATH, SEE https://xrpl.org/cryptographic-keys.html#key-derivation 33 | 34 | let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN))! 35 | 36 | // derive the root secret key 37 | var rootSecretKey = findSecretKey(ctx: ctx, startingKey: seed) 38 | 39 | // derive the root public key 40 | let rootPublicKey = try derivePublicKey(ctx: ctx, secretKey: rootSecretKey.getPointer()) 41 | 42 | // derive intermediate secret key 43 | let tr: [UInt8] = [0, 0, 0, 0] 44 | var intermediateSecretKey = findSecretKey(ctx: ctx, startingKey: rootPublicKey.compressed + tr) 45 | 46 | // derive intermediate public key (useful for debugging) 47 | _ = try derivePublicKey(ctx: ctx, secretKey: intermediateSecretKey.getPointer()) 48 | 49 | // derive master private key 50 | let bigRootPrivate = BigUInt(rootSecretKey.toHexString(), radix: 16)! 51 | let bigIntermediatePrivate = BigUInt(intermediateSecretKey.toHexString(), radix: 16)! 52 | let groupOrder = BigUInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)! 53 | let masterPrivateKey = ((bigRootPrivate + bigIntermediatePrivate) % groupOrder).serialize() 54 | var finalMasterPrivateKey = Data(repeating: 0x00, count: 33) 55 | finalMasterPrivateKey.replaceSubrange(1...masterPrivateKey.count, with: masterPrivateKey) 56 | 57 | // derive master public key 58 | var masterPrivateKeyForDerivation = Data(repeating: 0x00, count: 32) 59 | masterPrivateKeyForDerivation.replaceSubrange(32-masterPrivateKey.count...31, with: masterPrivateKey) 60 | let masterPublicKey = try derivePublicKey(ctx: ctx, secretKey: masterPrivateKeyForDerivation.getPointer()).compressed.toHexString() 61 | 62 | secp256k1_context_destroy(ctx) 63 | 64 | return XRPKeyPair(privateKey: finalMasterPrivateKey.toHexString(), publicKey: masterPublicKey) 65 | 66 | } 67 | 68 | private static func findSecretKey(ctx: OpaquePointer, startingKey: [UInt8], sequence: UInt32 = 0) -> Data { 69 | var potentialKey = Data(startingKey + sequence.bigEndian.data).sha512Half() 70 | if secp256k1_ec_seckey_verify(ctx, potentialKey.getPointer()) == 0 || potentialKey.checksum() == 0 { 71 | // invalid secret key, increment sequence 72 | return findSecretKey(ctx: ctx, startingKey: startingKey, sequence: sequence+1) 73 | } else { 74 | return potentialKey 75 | } 76 | } 77 | 78 | internal static func derivePublicKey(ctx: OpaquePointer, secretKey: UnsafePointer) throws -> ECDSAPublicKey { 79 | var _publicKey = secp256k1_pubkey() 80 | if secp256k1_ec_pubkey_create(ctx, UnsafeMutablePointer(&_publicKey), secretKey) == 0 { 81 | secp256k1_context_destroy(ctx) 82 | throw SECP256K1Error.derivationFailed 83 | } 84 | 85 | let uncompressedPublicKey = try serializePublicKey(ctx: ctx, publicKey: _publicKey, compressed: false) 86 | let compressedPublicKey = try serializePublicKey(ctx: ctx, publicKey: _publicKey, compressed: true) 87 | 88 | return ECDSAPublicKey(raw: _publicKey, uncompressed: uncompressedPublicKey, compressed: compressedPublicKey) 89 | } 90 | 91 | private static func serializePublicKey(ctx: OpaquePointer, publicKey: secp256k1_pubkey, compressed: Bool) throws -> [UInt8] { 92 | var _publicKey = publicKey 93 | let count = compressed ? 33 : 65 94 | var publicKey: [UInt8] = Array(repeating: 0, count: count) 95 | var size = publicKey.count 96 | let flags = compressed ? UInt32(SECP256K1_EC_COMPRESSED) : UInt32(SECP256K1_EC_UNCOMPRESSED) 97 | if secp256k1_ec_pubkey_serialize(ctx, &publicKey[0], &size, &_publicKey, flags) == 0 { 98 | secp256k1_context_destroy(ctx) 99 | throw SECP256K1Error.derivationFailed 100 | } 101 | return publicKey 102 | } 103 | 104 | static func sign(message: [UInt8], privateKey: [UInt8]) throws -> [UInt8] { 105 | 106 | let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN)) 107 | var sig = secp256k1_ecdsa_signature() 108 | 109 | // remove one byte prefix from primary key 110 | let privateKey = [UInt8](privateKey.suffix(from: 1)) 111 | 112 | var _privateKey = Data(privateKey) 113 | var _data = Data(sha512HalfHash(data: message)) 114 | 115 | if secp256k1_ecdsa_sign(ctx!, &sig, _data.getPointer(), _privateKey.getPointer(), secp256k1_nonce_function_rfc6979, nil) == 0 { 116 | secp256k1_context_destroy(ctx) 117 | throw SigningError.invalidPrivateKey 118 | } 119 | 120 | var tmp: [UInt8] = Array(repeating: 0, count: 72) 121 | var size = tmp.count 122 | if secp256k1_ecdsa_signature_serialize_der(ctx!, &tmp[0], &size, &sig) == 0 { 123 | secp256k1_context_destroy(ctx) 124 | throw SigningError.invalidSignature 125 | } 126 | 127 | secp256k1_context_destroy(ctx) 128 | return [UInt8](tmp.prefix(through: size-1)) 129 | 130 | } 131 | 132 | static func verify(signature: [UInt8], message: [UInt8], publicKey: [UInt8]) throws -> Bool { 133 | 134 | let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_VERIFY)) 135 | var sig = secp256k1_ecdsa_signature() 136 | 137 | var _signatureData = Data(signature) 138 | var _pubKeyData = Data(publicKey) 139 | var _msgDigest = Data(sha512HalfHash(data: message)) 140 | 141 | if secp256k1_ecdsa_signature_parse_der(ctx!, &sig, _signatureData.getPointer(), _signatureData.count) == 0 { 142 | secp256k1_context_destroy(ctx) 143 | throw SigningError.invalidSignature 144 | } 145 | 146 | var pubKey = secp256k1_pubkey() 147 | let resultParsePublicKey = secp256k1_ec_pubkey_parse(ctx!, &pubKey, _pubKeyData.getPointer(), 148 | _pubKeyData.count) 149 | if resultParsePublicKey == 0 { 150 | secp256k1_context_destroy(ctx) 151 | throw SigningError.invalidPublicKey 152 | } 153 | 154 | let result = secp256k1_ecdsa_verify(ctx!, &sig, _msgDigest.getPointer(), &pubKey) 155 | 156 | 157 | secp256k1_context_destroy(ctx) 158 | 159 | if result == 1 { 160 | return true 161 | } else { 162 | return false 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | Version 10 | 11 | 12 | License 13 | 14 | 15 | Platform 16 | 17 | 18 | CocoaPods and SPM compatible 19 | 20 | 21 | Travis Build Status 22 | 23 |

24 | 25 | # XRPKit 26 | 27 | XRPKit is a Swift SDK built for interacting with the XRP Ledger. XRPKit supports offline wallet creation, offline transaction creation/signing, and submitting transactions to the XRP ledger. XRPKit supports both the secp256k1 and ed25519 algorithms. XRPKit is available on iOS, macOS and Linux. WIP - use at your own risk. 28 | 29 | ## Author 30 | 31 | MitchLang009, mitch.s.lang@gmail.com 32 | 33 | ## License 34 | 35 | XRPKit is available under the MIT license. See the LICENSE file for more info. 36 | 37 | ## Installation 38 | 39 | #### CocoaPods 40 | 41 | XRPKit is available through [CocoaPods](https://cocoapods.org). To install 42 | it, simply add the following line to your Podfile: 43 | 44 | ```ruby 45 | pod 'XRPKit' 46 | ``` 47 | #### Swift Package Manager 48 | 49 | You can use [The Swift Package Manager](https://swift.org/package-manager) to 50 | install `XRPKit` by adding it to your `Package.swift` file: 51 | 52 | ```swift 53 | // swift-tools-version:5.1 54 | import PackageDescription 55 | 56 | let package = Package( 57 | name: "YOUR_PROJECT_NAME", 58 | dependencies: [ 59 | .package(url: "https://github.com/MitchLang009/XRPKit.git", from: "0.3.0"), 60 | ] 61 | ) 62 | ``` 63 | 64 | ## Linux Compatibility 65 | 66 | One of the goals of this library is to provide cross-platform support for Linux and support server-side 67 | Swift, however some features may only be available in iOS/macOS due to a lack of Linux supported 68 | libraries (ex. WebSockets). A test_linux.sh file is included that will run tests in a docker container. All 69 | contributions must compile on Linux. 70 | 71 | ## Wallets 72 | 73 | ### Create a new wallet 74 | 75 | ```swift 76 | 77 | import XRPKit 78 | 79 | // create a completely new, randomly generated wallet 80 | let wallet = XRPSeedWallet() // defaults to secp256k1 81 | let wallet2 = XRPSeedWallet(type: .secp256k1) 82 | let wallet3 = XRPSeedWallet(type: .ed25519) 83 | 84 | ``` 85 | 86 | ### Derive wallet from a seed 87 | 88 | ```swift 89 | 90 | import XRPKit 91 | 92 | // generate a wallet from an existing seed 93 | let wallet = try! XRPSeedWallet(seed: "snsTnz4Wj8vFnWirNbp7tnhZyCqx9") 94 | 95 | ``` 96 | 97 | ### Derive wallet from a mnemonic (BIP32/39/44) 98 | 99 | ```swift 100 | 101 | import XRPKit 102 | 103 | let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" 104 | let walletFromMnemonic = try! XRPMnemonicWallet(mnemonic: mnemonic) 105 | 106 | ``` 107 | 108 | ### Wallet properties 109 | ```swift 110 | 111 | import XRPKit 112 | 113 | let wallet = XRPSeedWallet() 114 | 115 | print(wallet.address) // rJk1prBA4hzuK21VDK2vK2ep2PKGuFGnUD 116 | print(wallet.seed) // snsTnz4Wj8vFnWirNbp7tnhZyCqx9 117 | print(wallet.publicKey) // 02514FA7EF3E9F49C5D4C487330CC8882C0B4381BEC7AC61F1C1A81D5A62F1D3CF 118 | print(wallet.privateKey) // 003FC03417669696AB4A406B494E6426092FD9A42C153E169A2B469316EA4E96B7 119 | 120 | ``` 121 | 122 | ### Validation 123 | ```swift 124 | 125 | import XRPKit 126 | 127 | // Address 128 | let btc = "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" 129 | let xrp = "rPdCDje24q4EckPNMQ2fmUAMDoGCCu3eGK" 130 | 131 | XRPSeedWallet.validate(address: btc) // returns false 132 | XRPSeedWallet.validate(address: xrp) // returns true 133 | 134 | // Seed 135 | let seed = "shrKftFK3ZkMPkq4xe5wGB8HaNSLf" 136 | 137 | XRPSeedWallet.validate(seed: xrp) // returns false 138 | XRPSeedWallet.validate(seed: seed) // returns true 139 | 140 | ``` 141 | 142 | ## Transactions 143 | 144 | ### Sending XRP 145 | ```swift 146 | 147 | import XRPKit 148 | 149 | let wallet = try! XRPSeedWallet(seed: "shrKftFK3ZkMPkq4xe5wGB8HaNSLf") 150 | let amount = try! XRPAmount(drops: 100000000) 151 | let address = try! XRPAddress(rAddress: "rPdCDje24q4EckPNMQ2fmUAMDoGCCu3eGK") 152 | 153 | _ = XRPPayment(from: wallet, to: address, amount: amount).send().map { (result) in 154 | print(result) 155 | } 156 | 157 | ``` 158 | 159 | ### Sending XRP with custom fields 160 | ```swift 161 | 162 | import XRPKit 163 | 164 | let wallet = try! XRPSeedWallet(seed: "shrKftFK3ZkMPkq4xe5wGB8HaNSLf") 165 | 166 | let fields: [String:Any] = [ 167 | "TransactionType" : "Payment", 168 | "Account" : wallet.address, 169 | "Destination" : "rPdCDje24q4EckPNMQ2fmUAMDoGCCu3eGK", 170 | "Amount" : "10000000", 171 | "Flags" : 2147483648, 172 | "LastLedgerSequence" : 951547, 173 | "Fee" : "40", 174 | "Sequence" : 11, 175 | ] 176 | 177 | // create the transaction (offline) 178 | let transaction = XRPRawTransaction(fields: fields) 179 | 180 | // sign the transaction (offline) 181 | let signedTransaction = try! transaction.sign(wallet: wallet) 182 | 183 | // submit the transaction (online) 184 | _ = signedTransaction.submit().map { (result) in 185 | print(result) 186 | } 187 | 188 | ``` 189 | 190 | 191 | ### Sending XRP with autofilled fields 192 | 193 | ```swift 194 | 195 | import XRPKit 196 | 197 | let wallet = try! XRPSeedWallet(seed: "shrKftFK3ZkMPkq4xe5wGB8HaNSLf") 198 | 199 | // dictionary containing partial transaction fields 200 | let partialFields: [String:Any] = [ 201 | "TransactionType" : "Payment", 202 | "Destination" : "rPdCDje24q4EckPNMQ2fmUAMDoGCCu3eGK", 203 | "Amount" : "100000000", 204 | "Flags" : 2147483648, 205 | ] 206 | 207 | // create the transaction from dictionary 208 | let partialTransaction = XRPTransaction(wallet: wallet, fields: partialFields) 209 | 210 | // autofills missing transaction fields (online) 211 | // signs transaction (offline) 212 | // submits transaction (online) 213 | _ = partialTransaction.send().map { (txResult) in 214 | print(txResult) 215 | } 216 | 217 | ``` 218 | 219 | ### Transaction Result 220 | 221 | ```swift 222 | 223 | // SUCCESS: { 224 | // result = { 225 | // "engine_result" = tesSUCCESS; 226 | // "engine_result_code" = 0; 227 | // "engine_result_message" = "The transaction was applied. Only final in a validated ledger."; 228 | // status = success; 229 | // "tx_blob" = 12000022800000002400000008201B000E83A6614000000005F5E100684000000000000028732102890EDF51199AEB1815324BA985C192D369B324AF6ABC1EBAD450E07EFBF5997E7446304402203765F06FB1D1D9FE942680A39C0925E95DC0AE18893268FDB5AF3CAFC5F6A87802201EFCE19E9C7ABBDD7C73F651A9AF6A323DDB4CE060A4CB63866512365830BEED81142B2DFB7FF7A2E9D8022144727A06141E4B3907248314F841A55DBAB1296D9A95F4CA8C05B721C1B0585C; 230 | // "tx_json" = { 231 | // Account = rhAK9w7X64AaZqSWEhajcq5vhGtxEcaUS7; 232 | // Amount = 100000000; 233 | // Destination = rPdCDje24q4EckPNMQ2fmUAMDoGCCu3eGK; 234 | // Fee = 40; 235 | // Flags = 2147483648; 236 | // LastLedgerSequence = 951206; 237 | // Sequence = 8; 238 | // SigningPubKey = 02890EDF51199AEB1815324BA985C192D369B324AF6ABC1EBAD450E07EFBF5997E; 239 | // TransactionType = Payment; 240 | // TxnSignature = 304402203765F06FB1D1D9FE942680A39C0925E95DC0AE18893268FDB5AF3CAFC5F6A87802201EFCE19E9C7ABBDD7C73F651A9AF6A323DDB4CE060A4CB63866512365830BEED; 241 | // hash = 4B709C7DFA8F8F396E4BB2CEACAFD61CA07000940736971AA788754267EE69AD; 242 | // }; 243 | // }; 244 | // } 245 | 246 | ``` 247 | 248 | ## Ledger Info 249 | 250 | ### Check balance 251 | ```swift 252 | 253 | import XRPKit 254 | 255 | _ = XRPLedger.getBalance(address: "rPdCDje24q4EckPNMQ2fmUAMDoGCCu3eGK").map { (amount) in 256 | print(amount.prettyPrinted()) // 1,800.000000 257 | } 258 | 259 | ``` 260 | 261 | ## WebSocket Support 262 | 263 | WebSockets are only supported on Apple platforms through URLSessionWebSocketTask. On Linux XRPLedger.ws is unavailable. Support for Linux 264 | will be possible with the availability of a WebSocket client library. 265 | 266 | More functionality to come. 267 | 268 | ### Example Command 269 | ```swift 270 | 271 | import XRPKit 272 | 273 | XRPLedger.ws.delegate = self // XRPWebSocketDelegate 274 | XRPLedger.ws.connect(url: .xrpl_ws_Devnet) 275 | let parameters: [String: Any] = [ 276 | "id" : "test", 277 | "method" : "fee" 278 | ] 279 | let data = try! JSONSerialization.data(withJSONObject: parameters, options: []) 280 | XRPLedger.ws.send(data: data) 281 | 282 | ``` 283 | 284 | ### Transaction Stream Request 285 | ```swift 286 | 287 | import XRPKit 288 | 289 | XRPLedger.ws.delegate = self // XRPWebSocketDelegate 290 | XRPLedger.ws.connect(url: .xrpl_ws_Devnet) 291 | XRPLedger.ws.subscribe(account: "r34XnDB2zS11NZ1wKJzpU1mjWExGVugTaQ") 292 | 293 | ``` 294 | 295 | ### Responses/Streams and XRPWebSocketDelegate 296 | 297 | ```swift 298 | 299 | import XRPKit 300 | 301 | class MyClass: XRPWebSocketDelegate { 302 | 303 | func onConnected(connection: XRPWebSocket) { 304 | 305 | } 306 | 307 | func onDisconnected(connection: XRPWebSocket, error: Error?) { 308 | 309 | } 310 | 311 | func onError(connection: XRPWebSocket, error: Error) { 312 | 313 | } 314 | 315 | func onResponse(connection: XRPWebSocket, response: XRPWebSocketResponse) { 316 | 317 | } 318 | 319 | func onStream(connection: XRPWebSocket, object: NSDictionary) { 320 | 321 | } 322 | 323 | } 324 | 325 | ``` 326 | -------------------------------------------------------------------------------- /Sources/XRPKit/Models/XRPWallet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Account.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/10/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SeedError: Error { 11 | case invalidSeed 12 | } 13 | 14 | public enum KeyPairError: Error { 15 | case invalidPrivateKey 16 | } 17 | 18 | public enum SeedType { 19 | case ed25519 20 | case secp256k1 21 | 22 | var algorithm: SigningAlgorithm.Type { 23 | switch self { 24 | case .ed25519: 25 | return ED25519.self 26 | case .secp256k1: 27 | return SECP256K1.self 28 | } 29 | } 30 | 31 | } 32 | 33 | public protocol XRPWallet { 34 | var privateKey: String {get} 35 | var publicKey: String {get} 36 | var address: String {get} 37 | var accountID: [UInt8] {get} 38 | init() 39 | static func deriveAddress(publicKey: String) -> String 40 | static func accountID(for address: String) -> [UInt8] 41 | static func validate(address: String) -> Bool 42 | } 43 | 44 | extension XRPWallet { 45 | public var accountID: [UInt8] { 46 | let accountID = RIPEMD160.hash(message: Data(hex: self.publicKey).sha256()) 47 | return [UInt8](accountID) 48 | } 49 | 50 | /// Derive a standard XRP address from a public key. 51 | /// 52 | /// - Parameter publicKey: hexadecimal public key 53 | /// - Returns: standard XRP address encoded using XRP alphabet 54 | /// 55 | public static func deriveAddress(publicKey: String) -> String { 56 | let accountID = RIPEMD160.hash(message: Data(hex: publicKey).sha256()) 57 | let prefixedAccountID = Data([0x00]) + accountID 58 | let checksum = Data(prefixedAccountID).sha256().sha256().prefix(through: 3) 59 | let addrrssData = prefixedAccountID + checksum 60 | let address = String(base58Encoding: addrrssData) 61 | return address 62 | } 63 | 64 | public static func accountID(for address: String) -> [UInt8] { 65 | let data = Data(base58Decoding: address)! 66 | let withoutCheck = data.prefix(data.count-4) 67 | let withoutPrefix = withoutCheck.suffix(from: 1) 68 | return withoutPrefix.bytes 69 | } 70 | 71 | /// Validates a String is a valid XRP address. 72 | /// 73 | /// - Parameter address: address encoded using XRP alphabet 74 | /// - Returns: true if valid 75 | /// 76 | public static func validate(address: String) -> Bool { 77 | if address.first != "r" { 78 | return false 79 | } 80 | if address.count < 25 || address.count > 35 { 81 | return false 82 | } 83 | if let _addressData = Data(base58Decoding: address) { 84 | var addressData = [UInt8](_addressData) 85 | // FIXME: base58Decoding 86 | addressData[0] = 0 87 | let accountID = [UInt8](addressData.prefix(addressData.count-4)) 88 | let checksum = [UInt8](addressData.suffix(4)) 89 | let _checksum = [UInt8](Data(accountID).sha256().sha256().prefix(through: 3)) 90 | if checksum == _checksum { 91 | return true 92 | } 93 | } 94 | return false 95 | } 96 | } 97 | 98 | public class XRPMnemonicWallet: XRPWallet { 99 | 100 | public var privateKey: String 101 | public var publicKey: String 102 | public var address: String 103 | public var mnemonic: String 104 | 105 | required public convenience init() { 106 | let mnemonic = try! Bip39Mnemonic.create() 107 | try! self.init(mnemonic: mnemonic) 108 | } 109 | 110 | /// Generates an XRPWallet from an mnemonic string. 111 | /// 112 | /// - Parameter mnemonic: mnemonic phrase . 113 | /// - Throws: SeedError 114 | public convenience init(mnemonic: String, account: UInt32 = 0, change: UInt32 = 0, addressIndex: UInt32 = 0) throws { 115 | let seed = Bip39Mnemonic.createSeed(mnemonic: mnemonic) 116 | let privateKey = PrivateKey(seed: seed, coin: .bitcoin) 117 | 118 | // BIP44 key derivation 119 | // m/44' 120 | let purpose = privateKey.derived(at: .hardened(44)) 121 | // m/44'/144' 122 | let coinType = purpose.derived(at: .hardened(144)) 123 | // m/44'/144'/0' 124 | let account = coinType.derived(at: .hardened(account)) 125 | // m/44'/144'/0'/0 126 | let change = account.derived(at: .notHardened(change)) 127 | // m/44'/144'/0'/0/0 128 | let firstPrivateKey = change.derived(at: .notHardened(addressIndex)) 129 | 130 | var finalMasterPrivateKey = Data(repeating: 0x00, count: 33) 131 | finalMasterPrivateKey.replaceSubrange(1...firstPrivateKey.raw.count, with: firstPrivateKey.raw) 132 | let address = XRPSeedWallet.deriveAddress(publicKey: firstPrivateKey.publicKey.hexadecimal) 133 | self.init( 134 | privateKey: finalMasterPrivateKey.hexadecimal, 135 | publicKey: firstPrivateKey.publicKey.hexadecimal, 136 | mnemonic: mnemonic, 137 | address: address 138 | ) 139 | } 140 | 141 | private init(privateKey: String, publicKey: String, mnemonic: String, address: String) { 142 | self.privateKey = privateKey.uppercased() 143 | self.publicKey = publicKey.uppercased() 144 | self.mnemonic = mnemonic 145 | self.address = address 146 | } 147 | 148 | public static func generateRandomMnemonicWallet() throws -> XRPWallet { 149 | let mnemonic = try Bip39Mnemonic.create() 150 | return try XRPMnemonicWallet(mnemonic: mnemonic) 151 | } 152 | } 153 | 154 | public class XRPSeedWallet: XRPWallet { 155 | 156 | public var privateKey: String 157 | public var publicKey: String 158 | public var seed: String 159 | public var address: String 160 | 161 | public required convenience init() { 162 | let entropy = Entropy() 163 | self.init(entropy: entropy, type: .secp256k1) 164 | } 165 | 166 | public convenience init(type: SeedType = .secp256k1) { 167 | let entropy = Entropy() 168 | self.init(entropy: entropy, type: type) 169 | } 170 | 171 | private init(privateKey: String, publicKey: String, seed: String, address: String) { 172 | self.privateKey = privateKey 173 | self.publicKey = publicKey 174 | self.seed = seed 175 | self.address = address 176 | } 177 | 178 | private convenience init(entropy: Entropy, type: SeedType) { 179 | switch type { 180 | case .ed25519: 181 | let keyPair = try! ED25519.deriveKeyPair(seed: entropy.bytes) 182 | let publicKey = [0xED] + keyPair.publicKey.hexadecimal! 183 | let seed = try! XRPSeedWallet.encodeSeed(entropy: entropy, type: .ed25519) 184 | let address = XRPSeedWallet.deriveAddress(publicKey: publicKey.toHexString()) 185 | self.init(privateKey: keyPair.privateKey, publicKey: publicKey.toHexString(), seed: seed, address: address) 186 | case .secp256k1: 187 | let keyPair = try! SECP256K1.deriveKeyPair(seed: entropy.bytes) 188 | let seed = try! XRPSeedWallet.encodeSeed(entropy: entropy, type: .secp256k1) 189 | let address = XRPSeedWallet.deriveAddress(publicKey: keyPair.publicKey) 190 | self.init(privateKey: keyPair.privateKey, publicKey: keyPair.publicKey, seed: seed, address: address) 191 | } 192 | } 193 | 194 | /// Generates an XRPWallet from an existing family seed. 195 | /// 196 | /// - Parameter seed: amily seed using XRP alphabet and standard format. 197 | /// - Throws: SeedError 198 | public convenience init(seed: String) throws { 199 | let bytes = try XRPSeedWallet.decodeSeed(seed: seed)! 200 | let entropy = Entropy(bytes: bytes) 201 | let type = seed.prefix(3) == "sEd" ? SeedType.ed25519 : SeedType.secp256k1 202 | self.init(entropy: entropy, type: type) 203 | } 204 | 205 | private static func encodeSeed(entropy: Entropy, type: SeedType) throws -> String { 206 | // [0x01, 0xE1, 0x4B] = sEd, [0x21] = s 207 | // see ripple/ripple-keypairs 208 | let version: [UInt8] = type == .ed25519 ? [0x01, 0xE1, 0x4B] : [0x21] 209 | let versionEntropy: [UInt8] = version + entropy.bytes 210 | let check = [UInt8](Data(versionEntropy).sha256().sha256().prefix(through: 3)) 211 | let versionEntropyCheck: [UInt8] = versionEntropy + check 212 | return String(base58Encoding: Data(versionEntropyCheck), alphabet: Base58String.xrpAlphabet) 213 | } 214 | 215 | private static func decodeSeed(seed: String) throws -> [UInt8]? { 216 | // make sure seed will at least parse for checksum validation 217 | // FIXME: this needs work 218 | if seed.count < 10 || Data(base58Decoding: seed) == nil || seed.first != "s" { 219 | throw SeedError.invalidSeed 220 | } 221 | let versionEntropyCheck = [UInt8](Data(base58Decoding: seed)!) 222 | let check = Array(versionEntropyCheck.suffix(4)) 223 | let versionEntropy = versionEntropyCheck.prefix(versionEntropyCheck.count-4) 224 | if check == [UInt8](Data(versionEntropy).sha256().sha256().prefix(through: 3)) { 225 | if versionEntropy[0] == 0x21 { 226 | // secp256k1 227 | let entropy = Array(versionEntropy.suffix(versionEntropy.count-1)) 228 | return entropy 229 | } else if versionEntropy[0] == 0x01 && versionEntropy[1] == 0xE1 && versionEntropy[2] == 0x4B { 230 | // ed25519 231 | let entropy = Array(versionEntropy.suffix(versionEntropy.count-3)) 232 | return entropy 233 | } 234 | } 235 | throw SeedError.invalidSeed 236 | } 237 | 238 | 239 | public static func getSeedTypeFrom(publicKey: String) -> SeedType { 240 | let data = [UInt8](publicKey.hexadecimal!) 241 | // FIXME: Is this correct? 242 | return data.count == 33 && data[0] == 0xED ? .ed25519 : .secp256k1 243 | } 244 | 245 | /// Validates a String is a valid XRP family seed. 246 | /// 247 | /// - Parameter seed: seed encoded using XRP alphabet 248 | /// - Returns: true if valid 249 | /// 250 | public static func validate(seed: String) -> Bool { 251 | do { 252 | if let _ = try XRPSeedWallet.decodeSeed(seed: seed) { 253 | return true 254 | } 255 | return false 256 | } catch { 257 | return false 258 | } 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /Sources/XRPKit/Network/XRPLedger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ledger.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/10/19. 6 | // 7 | 8 | import Foundation 9 | import NIO 10 | 11 | enum LedgerError: Error { 12 | case runtimeError(String) 13 | } 14 | 15 | public struct XRPLedger { 16 | 17 | // WebSocket is always available through SPM 18 | // WebSocket is only available through CocoaPods on newer OS 19 | #if canImport(WebSocketKit) 20 | public static var ws: XRPWebSocket = LinuxWebSocket() 21 | #elseif !os(Linux) 22 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 23 | public static var ws: XRPWebSocket = AppleWebSocket() 24 | #endif 25 | 26 | // JSON-RPC 27 | private static var url: URL = .xrpl_rpc_Testnet 28 | 29 | private init() { 30 | 31 | } 32 | 33 | public static func setURL(endpoint: URL) { 34 | self.url = endpoint 35 | } 36 | 37 | public static func getTxs(account: String) -> EventLoopFuture<[XRPHistoricalTransaction]> { 38 | 39 | let promise = eventGroup.next().makePromise(of: [XRPHistoricalTransaction].self) 40 | 41 | let parameters: [String: Any] = [ 42 | "method" : "account_tx", 43 | "params": [ 44 | [ 45 | "account" : account, 46 | "ledger_index_min" : -1, 47 | "ledger_index_max" : -1, 48 | ] 49 | ] 50 | ] 51 | 52 | _ = HTTP.post(url: url, parameters: parameters).map { (result) in 53 | let JSON = result as! NSDictionary 54 | let info = JSON["result"] as! NSDictionary 55 | let status = info["status"] as! String 56 | if status != "error" { 57 | let _array = info["transactions"] as! [NSDictionary] 58 | let filtered = _array.filter({ (dict) -> Bool in 59 | let validated = dict["validated"] as! Bool 60 | let tx = dict["tx"] as! NSDictionary 61 | let meta = dict["meta"] as! NSDictionary 62 | let res = meta["TransactionResult"] as! String 63 | let type = tx["TransactionType"] as! String 64 | return validated && type == "Payment" && res == "tesSUCCESS" 65 | }) 66 | 67 | let transactions = filtered.map({ (dict) -> XRPHistoricalTransaction in 68 | let tx = dict["tx"] as! NSDictionary 69 | let destination = tx["Destination"] as! String 70 | let source = tx["Account"] as! String 71 | let amount = tx["Amount"] as! String 72 | let timestamp = tx["date"] as! Int 73 | let date = Date(timeIntervalSince1970: 946684800+Double(timestamp)) 74 | let type = account == source ? "Sent" : "Received" 75 | let address = account == source ? destination : source 76 | return XRPHistoricalTransaction(type: type, address: address, amount: try! XRPAmount(drops: Int(amount)!), date: date, raw: tx) 77 | }) 78 | promise.succeed(transactions.sorted(by: { (lh, rh) -> Bool in 79 | lh.date > rh.date 80 | })) 81 | } else { 82 | let errorMessage = info["error_message"] as! String 83 | let error = LedgerError.runtimeError(errorMessage) 84 | promise.fail(error) 85 | } 86 | }.recover { (error) in 87 | promise.fail(error) 88 | } 89 | 90 | return promise.futureResult 91 | 92 | } 93 | 94 | public static func getBalance(address: String) -> EventLoopFuture { 95 | 96 | let promise = eventGroup.next().makePromise(of: XRPAmount.self) 97 | 98 | let parameters: [String: Any] = [ 99 | "method" : "account_info", 100 | "params": [ 101 | [ 102 | "account" : address 103 | ] 104 | ] 105 | ] 106 | _ = HTTP.post(url: url, parameters: parameters).map { (result) in 107 | let JSON = result as! NSDictionary 108 | let info = JSON["result"] as! NSDictionary 109 | let status = info["status"] as! String 110 | if status != "error" { 111 | let account = info["account_data"] as! NSDictionary 112 | let balance = account["Balance"] as! String 113 | let amount = try! XRPAmount(drops: Int(balance)!) 114 | promise.succeed( amount) 115 | } else { 116 | let errorMessage = info["error_message"] as! String 117 | let error = LedgerError.runtimeError(errorMessage) 118 | promise.fail(error) 119 | } 120 | }.recover { (error) in 121 | promise.fail(error) 122 | } 123 | 124 | return promise.futureResult 125 | } 126 | 127 | public static func getAccountInfo(account: String) -> EventLoopFuture { 128 | let promise = eventGroup.next().makePromise(of: XRPAccountInfo.self) 129 | let parameters: [String: Any] = [ 130 | "method" : "account_info", 131 | "params": [ 132 | [ 133 | "account" : account, 134 | "strict": true, 135 | "ledger_index": "current", 136 | "queue": true 137 | ] 138 | ] 139 | ] 140 | _ = HTTP.post(url: url, parameters: parameters).map { (result) in 141 | let JSON = result as! NSDictionary 142 | let info = JSON["result"] as! NSDictionary 143 | let status = info["status"] as! String 144 | if status != "error" { 145 | let account = info["account_data"] as! NSDictionary 146 | let balance = account["Balance"] as! String 147 | let address = account["Account"] as! String 148 | let sequence = account["Sequence"] as! Int 149 | let accountInfo = XRPAccountInfo(address: address, drops: Int(balance)!, sequence: sequence) 150 | promise.succeed( accountInfo) 151 | } else { 152 | let errorMessage = info["error_message"] as! String 153 | let error = LedgerError.runtimeError(errorMessage) 154 | promise.fail(error) 155 | } 156 | }.recover { (error) in 157 | promise.fail(error) 158 | } 159 | return promise.futureResult 160 | } 161 | 162 | public static func getSignerList(address: String) -> EventLoopFuture { 163 | 164 | let promise = eventGroup.next().makePromise(of: NSDictionary.self) 165 | 166 | let parameters: [String: Any] = [ 167 | "method" : "account_objects", 168 | "params": [ 169 | [ 170 | "account" : address, 171 | "ledger_index": "validated", 172 | "type": "signer_list", 173 | ] 174 | ] 175 | ] 176 | _ = HTTP.post(url: url, parameters: parameters).map { (result) in 177 | let JSON = result as! NSDictionary 178 | let info = JSON["result"] as! NSDictionary 179 | let status = info["status"] as! String 180 | if status != "error" { 181 | promise.succeed( info) 182 | } else { 183 | let errorMessage = info["error_message"] as! String 184 | let error = LedgerError.runtimeError(errorMessage) 185 | promise.fail(error) 186 | } 187 | }.recover { (error) in 188 | promise.fail(error) 189 | } 190 | 191 | return promise.futureResult 192 | 193 | } 194 | 195 | public static func getPendingEscrows(address: String) -> EventLoopFuture { 196 | 197 | let promise = eventGroup.next().makePromise(of: NSDictionary.self) 198 | 199 | let parameters: [String: Any] = [ 200 | "method" : "account_objects", 201 | "params": [ 202 | [ 203 | "account" : address, 204 | "ledger_index": "validated", 205 | "type": "escrow", 206 | ] 207 | ] 208 | ] 209 | _ = HTTP.post(url: url, parameters: parameters).map { (result) in 210 | let JSON = result as! NSDictionary 211 | let info = JSON["result"] as! NSDictionary 212 | let status = info["status"] as! String 213 | if status != "error" { 214 | promise.succeed( info) 215 | } else { 216 | let errorMessage = info["error_message"] as! String 217 | let error = LedgerError.runtimeError(errorMessage) 218 | promise.fail(error) 219 | } 220 | }.recover { (error) in 221 | promise.fail(error) 222 | } 223 | 224 | return promise.futureResult 225 | 226 | } 227 | 228 | public static func currentLedgerInfo() -> EventLoopFuture { 229 | let promise = eventGroup.next().makePromise(of: XRPCurrentLedgerInfo.self) 230 | let parameters: [String: Any] = [ 231 | "method" : "fee" 232 | ] 233 | _ = HTTP.post(url: url, parameters: parameters).map { (result) in 234 | let JSON = result as! NSDictionary 235 | let info = JSON["result"] as! NSDictionary 236 | let drops = info["drops"] as! NSDictionary 237 | let min = drops["minimum_fee"] as! String 238 | let max = drops["median_fee"] as! String 239 | let ledger = info["ledger_current_index"] as! Int 240 | let ledgerInfo = XRPCurrentLedgerInfo(index: ledger, minFee: Int(min)!, maxFee: Int(max)!) 241 | promise.succeed( ledgerInfo) 242 | }.recover { (error) in 243 | promise.fail(error) 244 | } 245 | return promise.futureResult 246 | } 247 | 248 | public static func submit(txBlob: String) -> EventLoopFuture { 249 | let promise = eventGroup.next().makePromise(of: NSDictionary.self) 250 | let parameters: [String: Any] = [ 251 | "method" : "submit", 252 | "params": [ 253 | [ 254 | "tx_blob": txBlob 255 | ] 256 | ] 257 | ] 258 | _ = HTTP.post(url: url, parameters: parameters).map { (result) in 259 | let JSON = result as! NSDictionary 260 | let info = JSON["result"] as! NSDictionary 261 | promise.succeed( info) 262 | }.recover { (error) in 263 | promise.fail(error) 264 | } 265 | return promise.futureResult 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /Sources/XRPKit/SigningAlgorithms/ED25519 (pebble8888)/ed25519_sc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ed25519_sc.swift 3 | // 4 | // Copyright 2017 pebble8888. All rights reserved. 5 | // 6 | // This software is provided 'as-is', without any express or implied 7 | // warranty. In no event will the authors be held liable for any damages 8 | // arising from the use of this software. 9 | // 10 | // Permission is granted to anyone to use this software for any purpose, 11 | // including commercial applications, and to alter it and redistribute it 12 | // freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 19 | // 2. Altered source versions must be plainly marked as such, and must not be 20 | // misrepresented as being the original software. 21 | // 22 | // 3. This notice may not be removed or altered from any source distribution. 23 | // 24 | 25 | import Foundation 26 | 27 | struct shortsc { 28 | var v: [UInt32] // 16 29 | init() { 30 | v = [UInt32](repeating: 0, count: 16) 31 | } 32 | } 33 | 34 | struct sc { 35 | var v: [UInt32] // 32 36 | init() { 37 | v = [UInt32](repeating: 0, count: 32) 38 | } 39 | 40 | private static let k = 32 41 | 42 | /* Arithmetic modulo the group order 43 | order 44 | = 2^252 + 27742317777372353535851937790883648493 45 | = 7237005577332262213973186563042994240857116359379907606001950938285454250989 46 | = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed 47 | 48 | p = 2^255 - 19 49 | = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed 50 | */ 51 | 52 | // little endian group order m 53 | private static let m: [UInt32] = 54 | [0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58, 0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10] 56 | 57 | /* 58 | for barrett_reduce algorithm 59 | b = 256 = 2^8 60 | k = 32 = 2^5 61 | b^(2k) = (2^8)^64 = 2^512 62 | mu = 2^512 // m 63 | = 1852673427797059126777135760139006525645217721299241702126143248052143860224795 64 | = 0x0fffffffffffffffffffffffffffffffeb2106215d086329a7ed9ce5a30a2c131b 65 | */ 66 | private static let mu: [UInt32] = 67 | [0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED, 0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, 0x21, 68 | 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F] 69 | 70 | private static func lt(_ a: UInt32, _ b: UInt32) -> UInt32 /* 16-bit inputs */ { 71 | if a < b { 72 | return 1 73 | } else { 74 | return 0 75 | } 76 | } 77 | 78 | /// if r > m: r = r - m 79 | /// else : r = r 80 | private static func reduce_add_sub(_ r: inout sc) { 81 | var val: UInt32 = 0 82 | var borrow: UInt32 = 0 83 | // r - m 84 | var t = [UInt8](repeating: 0, count: 32) 85 | 86 | for i in 0..= 0 && vv <= 0xff) 91 | t[i] = UInt8(vv) 92 | val = borrow 93 | } 94 | // no borrow: mask = 0xffffffff -> r = r - m 95 | // borrow : mask = 0x0 -> r = r 96 | let mask = UInt32(bitPattern: Int32(borrow)-1) 97 | for i in 0..= k-1 { 119 | q2[i+j] += x[j+k-1] * mu[i] 120 | } 121 | } 122 | } 123 | 124 | // q3 = floor(q2 / b^(k+1)) 125 | // q3 = (... + b^(k+1) * q2[k+1] + b^(k+2) * q2[k+2] + ... + b^(2k) * q2[2k] + b^(2k+1) * q2[2k+1]) 126 | // = q2[k+1] + b^1 * q2[k+1] + ... + b^(k-1) * q2[2k] + b^k * q2[2k+1] 127 | // Since q2[2k] has carry q2[2k+1] is zero. 128 | let carry1 = q2[k-1] >> 8 129 | q2[k] += carry1 130 | let carry2 = q2[k] >> 8 131 | q2[k+1] += carry2 132 | 133 | // STEP2,3 134 | // r1 = x (mod b^(k+1)) 135 | var r1 = [UInt32](repeating: 0, count: k+1) 136 | for i in 0...k { 137 | r1[i] = x[i] 138 | } 139 | 140 | // r2 = q3 * m (mod b^(k+1)) 141 | var r2 = [UInt32](repeating: 0, count: k+1) 142 | for i in 0...k-1 { 143 | for j in 0...k { 144 | if i+j < k+1 { 145 | r2[i+j] += q2[j+k+1] * m[i] 146 | } 147 | } 148 | } 149 | for i in 0...k-1 { 150 | let carry = r2[i] >> 8 151 | r2[i+1] += carry 152 | r2[i] &= 0xff 153 | } 154 | r2[k] &= 0xff 155 | 156 | // r = r1 - r2 (or + b^(k+1)) 157 | // last borrow means STEP3 for r < 0 158 | // r = (Q-q3) * m + R <= 2m 159 | // 2m = 2 * (0x10 * b^(k-1) + ...) = 0x20 * b^(k-1) + ... < b^k 160 | // so r can represented for b^0 y\_0 + b^1 y\_1 + ... + b^(k-1) y\_(k-1) 161 | // it means r[v] is zero 162 | var val: UInt32 = 0 163 | for i in 0...k-1 { 164 | val += r2[i] 165 | let borrow = lt(r1[i], val) 166 | let vv = Int64(r1[i]) - Int64(val) + Int64(borrow << 8) 167 | assert(vv >= 0 && vv <= 0xff) 168 | r.v[i] = UInt32(vv) 169 | val = borrow 170 | } 171 | 172 | // STEP4: twice or once or none 173 | reduce_add_sub(&r) 174 | reduce_add_sub(&r) 175 | } 176 | 177 | // check x is [0, m) 178 | static func sc25519_less_order(_ x: [UInt8] /* 32 */) -> Bool { 179 | if x.count != k { 180 | return false 181 | } 182 | for i in (0.. m[i] { 187 | // large 188 | return false 189 | } 190 | } 191 | // equal to m 192 | return false 193 | } 194 | 195 | static func sc25519_from32bytes(_ r: inout sc, _ x: [UInt8] /* 32 */) { 196 | assert(x.count >= k) 197 | var t = [UInt32](repeating: 0, count: k*2) 198 | for i in 0..= 16) 210 | for i in 0..<16 { 211 | r.v[i] = UInt32(x[i]) 212 | } 213 | } 214 | 215 | static func sc25519_from64bytes(_ r: inout sc, _ x: [UInt8] /* 64 */) { 216 | assert(x.count == k*2) 217 | var t = [UInt32](repeating: 0, count: k*2) 218 | for i in 0.. Int { 242 | for i in 0.. Int { 251 | for i in stride(from: 31, to: 15, by: -1) { 252 | if x.v[i] != 0 { 253 | return 0 254 | } 255 | } 256 | return 1 257 | } 258 | 259 | static func sc25519_lt_vartime(_ x: sc, _ y: sc) -> UInt { 260 | for i in stride(from: 31, through: 0, by: -1) { 261 | if x.v[i] < y.v[i] { 262 | return 1 263 | } 264 | if x.v[i] > y.v[i] { 265 | return 0 266 | } 267 | } 268 | return 0 269 | } 270 | 271 | static func sc25519_add(_ r: inout sc, _ x: sc, _ y: sc) { 272 | var carry: UInt32 273 | for i in 0..> 8 278 | r.v[i+1] += carry 279 | r.v[i] &= 0xff 280 | } 281 | sc.reduce_add_sub(&r) 282 | } 283 | 284 | static func sc25519_sub_nored(_ r: inout sc, _ x: sc, _ y: sc) { 285 | var borrow: UInt32 = 0 286 | var t: UInt32 287 | for i in 0..> 8) & 1 291 | } 292 | } 293 | 294 | static func sc25519_mul(_ r: inout sc, _ x: sc, _ y: sc) { 295 | var t = [UInt32](repeating: 0, count: k*2) 296 | 297 | for i in 0..> 8 306 | t[i+1] += carry 307 | t[i] &= 0xff 308 | } 309 | 310 | sc.barrett_reduce(&r, t) 311 | } 312 | 313 | static func sc25519_mul_shortsc(_ r: inout sc, _ x: sc, _ y: shortsc) { 314 | var t = sc() 315 | sc25519_from_shortsc(&t, y) 316 | sc25519_mul(&r, x, t) 317 | } 318 | 319 | // divide to 3bits 320 | // 3 * 85 = 255 321 | static func sc25519_window3(_ r: inout [Int8] /* 85 */, _ s: sc) { 322 | assert(r.count == 85) 323 | for i in 0..<10 { 324 | r[8*i+0] = Int8(bitPattern: UInt8(s.v[3*i+0] & 7)) 325 | r[8*i+1] = Int8(bitPattern: UInt8((s.v[3*i+0] >> 3) & 7)) 326 | r[8*i+2] = Int8(bitPattern: UInt8((s.v[3*i+0] >> 6) & 7)) 327 | r[8*i+2] ^= Int8(bitPattern: UInt8((s.v[3*i+1] << 2) & 7)) 328 | r[8*i+3] = Int8(bitPattern: UInt8((s.v[3*i+1] >> 1) & 7)) 329 | r[8*i+4] = Int8(bitPattern: UInt8((s.v[3*i+1] >> 4) & 7)) 330 | r[8*i+5] = Int8(bitPattern: UInt8((s.v[3*i+1] >> 7) & 7)) 331 | r[8*i+5] ^= Int8(bitPattern: UInt8((s.v[3*i+2] << 1) & 7)) 332 | r[8*i+6] = Int8(bitPattern: UInt8((s.v[3*i+2] >> 2) & 7)) 333 | r[8*i+7] = Int8(bitPattern: UInt8((s.v[3*i+2] >> 5) & 7)) 334 | } 335 | let i = 10 336 | r[8*i+0] = Int8(bitPattern: UInt8(s.v[3*i+0] & 7)) 337 | r[8*i+1] = Int8(bitPattern: UInt8((s.v[3*i+0] >> 3) & 7)) 338 | r[8*i+2] = Int8(bitPattern: UInt8((s.v[3*i+0] >> 6) & 7)) 339 | r[8*i+2] ^= Int8(bitPattern: UInt8((s.v[3*i+1] << 2) & 7)) 340 | r[8*i+3] = Int8(bitPattern: UInt8((s.v[3*i+1] >> 1) & 7)) 341 | r[8*i+4] = Int8(bitPattern: UInt8((s.v[3*i+1] >> 4) & 7)) 342 | 343 | /* Making it signed */ 344 | var carry: Int8 = 0 345 | for i in 0..<84 { 346 | r[i] += carry 347 | r[i+1] += (r[i] >> 3) 348 | r[i] &= 7 349 | carry = r[i] >> 2 350 | let vv: Int16 = Int16(r[i]) - Int16(carry<<3) 351 | assert(vv >= -128 && vv <= 127) 352 | r[i] = Int8(vv) 353 | } 354 | r[84] += Int8(carry) 355 | } 356 | 357 | // scalar is less than 2^255 - 19, so 256bit value always zero. 358 | static func sc25519_2interleave2(_ r: inout [UInt8] /* 127 */, _ s1: sc, _ s2: sc) { 359 | assert(r.count == 127) 360 | for i in 0..<31 { 361 | let a1 = UInt8(s1.v[i] & 0xff) 362 | let a2 = UInt8(s2.v[i] & 0xff) 363 | // 8bits = 2bits * 4 364 | // s2 s1 365 | r[4*i] = ((a1 >> 0) & 3) ^ (((a2 >> 0) & 3) << 2) 366 | r[4*i+1] = ((a1 >> 2) & 3) ^ (((a2 >> 2) & 3) << 2) 367 | r[4*i+2] = ((a1 >> 4) & 3) ^ (((a2 >> 4) & 3) << 2) 368 | r[4*i+3] = ((a1 >> 6) & 3) ^ (((a2 >> 6) & 3) << 2) 369 | } 370 | 371 | let b1 = UInt8(s1.v[31] & 0xff) 372 | let b2 = UInt8(s2.v[31] & 0xff) 373 | r[124] = ((b1 >> 0) & 3) ^ (((b2 >> 0) & 3) << 2) 374 | r[125] = ((b1 >> 2) & 3) ^ (((b2 >> 2) & 3) << 2) 375 | r[126] = ((b1 >> 4) & 3) ^ (((b2 >> 4) & 3) << 2) 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /Sources/XRPKit/SigningAlgorithms/ED25519 (pebble8888)/ed25519_fe.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ed25519_fe.swift 3 | // 4 | // Copyright 2017 pebble8888. All rights reserved. 5 | // 6 | // This software is provided 'as-is', without any express or implied 7 | // warranty. In no event will the authors be held liable for any damages 8 | // arising from the use of this software. 9 | // 10 | // Permission is granted to anyone to use this software for any purpose, 11 | // including commercial applications, and to alter it and redistribute it 12 | // freely, subject to the following restrictions: 13 | // 14 | // 1. The origin of this software must not be misrepresented; you must not 15 | // claim that you wrote the original software. If you use this software 16 | // in a product, an acknowledgment in the product documentation would be 17 | // appreciated but is not required. 18 | // 19 | // 2. Altered source versions must be plainly marked as such, and must not be 20 | // misrepresented as being the original software. 21 | // 22 | // 3. This notice may not be removed or altered from any source distribution. 23 | // 24 | 25 | import Foundation 26 | 27 | // field element 28 | struct fe: CustomDebugStringConvertible { 29 | // WINDOWSIZE = 1, 8bit * 32 = 256bit 30 | // val = 2^(31*8) * v[31] 31 | // + 2^(30*8) * v[30] 32 | // + .. 33 | // + 2^(2*8) * v[2] 34 | // + 2^(1*8) * v[1] 35 | // + 2^(0*8) * v[0] 36 | public var v: [UInt32] // size:32 37 | 38 | public var debugDescription: String { 39 | return v.map({ String(format: "%d ", $0)}).joined() 40 | } 41 | 42 | public init() { 43 | v = [UInt32](repeating: 0, count: 32) 44 | } 45 | 46 | public init(_ v: [UInt32]) { 47 | assert(v.count == 32) 48 | self.v = v 49 | } 50 | 51 | /* 16-bit inputs */ 52 | static func equal(_ a: UInt32, _ b: UInt32) -> UInt32 { 53 | return a == b ? 1 : 0 54 | } 55 | 56 | // greater equal 57 | /* 16-bit inputs */ 58 | static func ge(_ a: UInt32, _ b: UInt32) -> UInt32 { 59 | return a >= b ? 1 : 0 60 | } 61 | 62 | // 19 * a = (2^4 + 2^1 + 2^0) * a 63 | static func times19(_ a: UInt32) -> UInt32 { 64 | return (a << 4) + (a << 1) + a 65 | } 66 | 67 | // 38 * a = (2^5 + 2^2 + 2^1) * a 68 | static func times38(_ a: UInt32) -> UInt32 { 69 | return (a << 5) + (a << 2) + (a << 1) 70 | } 71 | 72 | // q = 2^255 - 19 = 2^(31*8)*(2^7) - 19 73 | // 7fff ffff ffff ffff ffff ffff ffff ffff 74 | // ffff ffff ffff ffff ffff ffff ffff ffed 75 | // 0x7f = 0111 1111 = 2^7-1 76 | // 0xff = 1111 1111 = 2^8-1 77 | static func reduce_add_sub(_ r: inout fe) { 78 | var t: UInt32 79 | var s: UInt32 80 | // 32bit / 8bit = 4 81 | for _ in 0..<4 { 82 | // use q = 2^(31*8)*(2^7) - 19 83 | t = r.v[31] >> 7 84 | r.v[31] &= 0x7f 85 | t = times19(t) 86 | r.v[0] += t 87 | // move up 88 | for i in 0..<31 { 89 | s = r.v[i] >> 8 90 | r.v[i+1] += s 91 | r.v[i] &= 0xff 92 | } 93 | } 94 | } 95 | 96 | static func reduce_mul(_ r: inout fe) { 97 | var t: UInt32 98 | var s: UInt32 99 | for _ in 0..<2 { 100 | // use q = 2^(31*8)*(2^7) - 19 101 | t = r.v[31] >> 7 102 | r.v[31] &= 0x7f 103 | t = times19(t) 104 | r.v[0] += t 105 | // move up 106 | for i in 0..<31 { 107 | s = r.v[i] >> 8 108 | r.v[i+1] += s 109 | r.v[i] &= 0xff 110 | } 111 | } 112 | } 113 | 114 | /// reduction modulo 2^255-19 115 | /// 0x7f = 127 116 | /// 0xff = 255 117 | /// 0xed = 237 118 | static func fe25519_freeze(_ r: inout fe) { 119 | assert(r.v[31] <= 0xff) 120 | var m: UInt32 = equal(r.v[31], 127) 121 | for i in stride(from: 30, to: 0, by: -1) { 122 | m &= equal(r.v[i], 255) 123 | } 124 | m &= ge(r.v[0], 237) 125 | // Here if value is greater than q, m is 1 or 0. 126 | m = UInt32(bitPattern: Int32(m) * -1) 127 | // m is 0xffffffff or 0x0 128 | 129 | r.v[31] -= (m&127) 130 | for i in stride(from: 30, to: 0, by: -1) { 131 | r.v[i] -= m&255 132 | } 133 | r.v[0] -= m&237 134 | } 135 | 136 | static func fe25519_unpack(_ r: inout fe, _ x: [UInt8]/* 32 */) { 137 | assert(x.count == 32) 138 | for i in 0..<32 { 139 | r.v[i] = UInt32(x[i]) 140 | } 141 | r.v[31] &= 127 // remove parity 142 | } 143 | 144 | /// Assumes input x being reduced mod 2^255 145 | static func fe25519_pack(_ r: inout [UInt8] /* 32 or more */, _ x: fe) { 146 | assert(r.count >= 32) 147 | var y = x 148 | fe.fe25519_freeze(&y) 149 | for i in 0..<32 { 150 | r[i] = UInt8(y.v[i]) 151 | } 152 | } 153 | 154 | /// freeze input before calling iszero 155 | static func fe25519_iszero(_ x: fe) -> Bool { 156 | var t = x 157 | fe.fe25519_freeze(&t) 158 | var r = fe.equal(t.v[0], 0) 159 | for i in 1..<32 { 160 | r &= fe.equal(t.v[i], 0) 161 | } 162 | return r != 0 163 | } 164 | 165 | /// is equal after freeze 166 | static func fe25519_iseq_vartime(_ x: fe, _ y: fe) -> Bool { 167 | var t1 = x 168 | var t2 = y 169 | fe.fe25519_freeze(&t1) 170 | fe.fe25519_freeze(&t2) 171 | for i in 0..<32 { 172 | if t1.v[i] != t2.v[i] { 173 | return false 174 | } 175 | } 176 | return true 177 | } 178 | 179 | /// conditional move 180 | static func fe25519_cmov(_ r: inout fe, _ x: fe, _ b: UInt8) { 181 | let mask = UInt32(bitPattern: Int32(b) * -1) 182 | for i in 0..<32 { 183 | // ^ means xor 184 | r.v[i] ^= mask & (x.v[i] ^ r.v[i]) 185 | } 186 | } 187 | 188 | /// odd:1 even:0 189 | static func fe25519_getparity(_ x: fe) -> UInt8 { 190 | var t = x 191 | fe.fe25519_freeze(&t) 192 | return UInt8(t.v[0] & 1) 193 | } 194 | 195 | /// r = 1 196 | static func fe25519_setone(_ r: inout fe) { 197 | r.v[0] = 1 198 | for i in 1..<32 { 199 | r.v[i] = 0 200 | } 201 | } 202 | 203 | /// r = 0 204 | static func fe25519_setzero(_ r: inout fe) { 205 | for i in 0..<32 { 206 | r.v[i] = 0 207 | } 208 | } 209 | 210 | /// r = -x 211 | static func fe25519_neg(_ r: inout fe, _ x: fe) { 212 | var t = fe() 213 | for i in 0..<32 { 214 | t.v[i] = x.v[i] 215 | } 216 | fe25519_setzero(&r) 217 | fe25519_sub(&r, r, t) 218 | } 219 | 220 | /// r = x + y 221 | static func fe25519_add(_ r: inout fe, _ x: fe, _ y: fe) { 222 | for i in 0..<32 { 223 | r.v[i] = x.v[i] + y.v[i] 224 | } 225 | fe.reduce_add_sub(&r) 226 | } 227 | 228 | /// r = x - y 229 | /// q = 2 ** 255 - 19 230 | /// 7fff ffff ffff ffff ffff ffff ffff ffff 231 | /// ffff ffff ffff ffff ffff ffff ffff ffed 232 | /// 2 * 7f = fe 233 | /// 2 * ff = 1fe 234 | /// 2 * ed = 1da 235 | /// @note result is reduced 236 | static func fe25519_sub(_ r: inout fe, _ x: fe, _ y: fe) { 237 | // t = 2 * q + x 238 | var t = [UInt32](repeating: 0, count: 32) 239 | t[0] = x.v[0] + 0x1da // LSB 240 | for i in 1..<31 { t[i] = x.v[i] + 0x1fe } 241 | t[31] = x.v[31] + 0xfe // MSB 242 | // r = t - y 243 | for i in 0..<32 { r.v[i] = t[i] - y.v[i] } 244 | fe.reduce_add_sub(&r) 245 | } 246 | 247 | /// r = x * y 248 | static func fe25519_mul(_ r: inout fe, _ x: fe, _ y: fe) { 249 | var t = [UInt32](repeating: 0, count: 63) 250 | 251 | for i in 0..<32 { 252 | for j in 0..<32 { 253 | t[i+j] += x.v[i] * y.v[j] 254 | } 255 | } 256 | 257 | // 2q = 2^256 - 2*19 258 | // so 2^256 = 2*19 259 | for i in 32..<63 { 260 | r.v[i-32] = t[i-32] + fe.times38(t[i]) 261 | } 262 | r.v[31] = t[31] /* result now in r[0]...r[31] */ 263 | 264 | fe.reduce_mul(&r) 265 | } 266 | 267 | /// r = x^2 268 | static func fe25519_square(_ r: inout fe, _ x: fe) { 269 | fe25519_mul(&r, x, x) 270 | } 271 | 272 | /// r = 1/x 273 | /// q = 2^255-19 274 | /// 1/a = a^(q-2) 275 | /// q-2 = 2^255-21 276 | static func fe25519_invert(_ r: inout fe, _ x: fe) { 277 | var z2 = fe() 278 | var z9 = fe() 279 | var z11 = fe() 280 | var z2_5_0 = fe() 281 | var z2_10_0 = fe() 282 | var z2_20_0 = fe() 283 | var z2_50_0 = fe() 284 | var z2_100_0 = fe() 285 | var t0 = fe() 286 | var t1 = fe() 287 | 288 | /* 2 */ fe25519_square(&z2, x) 289 | /* 4 */ fe25519_square(&t1, z2) 290 | /* 8 */ fe25519_square(&t0, t1) 291 | /* 9 */ fe25519_mul(&z9, t0, x) 292 | /* 11 */ fe25519_mul(&z11, z9, z2) 293 | /* 22 */ fe25519_square(&t0, z11) 294 | /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, t0, z9) 295 | 296 | /* 2^6 - 2^1 */ fe25519_square(&t0, z2_5_0) 297 | /* 2^7 - 2^2 */ fe25519_square(&t1, t0) 298 | /* 2^8 - 2^3 */ fe25519_square(&t0, t1) 299 | /* 2^9 - 2^4 */ fe25519_square(&t1, t0) 300 | /* 2^10 - 2^5 */ fe25519_square(&t0, t1) 301 | /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, t0, z2_5_0) 302 | 303 | /* 2^11 - 2^1 */ fe25519_square(&t0, z2_10_0) 304 | /* 2^12 - 2^2 */ fe25519_square(&t1, t0) 305 | /* 2^20 - 2^10 */ for _ in stride(from: 2, to: 10, by: 2) { fe25519_square(&t0, t1); fe25519_square(&t1, t0) } 306 | /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, t1, z2_10_0) 307 | 308 | /* 2^21 - 2^1 */ fe25519_square(&t0, z2_20_0) 309 | /* 2^22 - 2^2 */ fe25519_square(&t1, t0) 310 | /* 2^40 - 2^20 */ for _ in stride(from: 2, to: 20, by: 2) { fe25519_square(&t0, t1); fe25519_square(&t1, t0) } 311 | /* 2^40 - 2^0 */ fe25519_mul(&t0, t1, z2_20_0) 312 | 313 | /* 2^41 - 2^1 */ fe25519_square(&t1, t0) 314 | /* 2^42 - 2^2 */ fe25519_square(&t0, t1) 315 | /* 2^50 - 2^10 */ for _ in stride(from: 2, to: 10, by: 2) { fe25519_square(&t1, t0); fe25519_square(&t0, t1) } 316 | /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0, t0, z2_10_0) 317 | 318 | /* 2^51 - 2^1 */ fe25519_square(&t0, z2_50_0) 319 | /* 2^52 - 2^2 */ fe25519_square(&t1, t0) 320 | /* 2^100 - 2^50 */ for _ in stride(from: 2, to: 50, by: 2) { fe25519_square(&t0, t1); fe25519_square(&t1, t0) } 321 | /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, t1, z2_50_0) 322 | 323 | /* 2^101 - 2^1 */ fe25519_square(&t1, z2_100_0) 324 | /* 2^102 - 2^2 */ fe25519_square(&t0, t1) 325 | /* 2^200 - 2^100 */ for _ in stride(from: 2, to: 100, by: 2) { fe25519_square(&t1, t0); fe25519_square(&t0, t1) } 326 | /* 2^200 - 2^0 */ fe25519_mul(&t1, t0, z2_100_0) 327 | 328 | /* 2^201 - 2^1 */ fe25519_square(&t0, t1) 329 | /* 2^202 - 2^2 */ fe25519_square(&t1, t0) 330 | /* 2^250 - 2^50 */ for _ in stride(from: 2, to: 50, by: 2) { fe25519_square(&t0, t1); fe25519_square(&t1, t0) } 331 | /* 2^250 - 2^0 */ fe25519_mul(&t0, t1, z2_50_0) 332 | 333 | /* 2^251 - 2^1 */ fe25519_square(&t1, t0) 334 | /* 2^252 - 2^2 */ fe25519_square(&t0, t1) 335 | /* 2^253 - 2^3 */ fe25519_square(&t1, t0) 336 | /* 2^254 - 2^4 */ fe25519_square(&t0, t1) 337 | /* 2^255 - 2^5 */ fe25519_square(&t1, t0) 338 | /* 2^255 - 21 */ fe25519_mul(&r, t1, z11) 339 | } 340 | 341 | /// q = 2^255-19 342 | /// (q-5)/8 = 2^252 - 3 343 | /// r = x ^ (2^252 - 3) 344 | static func fe25519_pow2523(_ r: inout fe, _ x: fe) { 345 | var z2 = fe() 346 | var z9 = fe() 347 | var z11 = fe() 348 | var z2_5_0 = fe() 349 | var z2_10_0 = fe() 350 | var z2_20_0 = fe() 351 | var z2_50_0 = fe() 352 | var z2_100_0 = fe() 353 | var t = fe() 354 | 355 | /* 2 */ fe25519_square(&z2, x) 356 | /* 4 */ fe25519_square(&t, z2) 357 | /* 8 */ fe25519_square(&t, t) 358 | /* 9 */ fe25519_mul(&z9, t, x) 359 | /* 11 */ fe25519_mul(&z11, z9, z2) 360 | /* 22 */ fe25519_square(&t, z11) 361 | /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, t, z9) 362 | 363 | /* 2^6 - 2^1 */ fe25519_square(&t, z2_5_0) 364 | /* 2^10 - 2^5 */ for _ in 1..<5 { fe25519_square(&t, t) } 365 | /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, t, z2_5_0) 366 | 367 | /* 2^11 - 2^1 */ fe25519_square(&t, z2_10_0) 368 | /* 2^20 - 2^10 */ for _ in 1..<10 { fe25519_square(&t, t) } 369 | /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, t, z2_10_0) 370 | 371 | /* 2^21 - 2^1 */ fe25519_square(&t, z2_20_0) 372 | /* 2^40 - 2^20 */ for _ in 1..<20 { fe25519_square(&t, t) } 373 | /* 2^40 - 2^0 */ fe25519_mul(&t, t, z2_20_0) 374 | 375 | /* 2^41 - 2^1 */ fe25519_square(&t, t) 376 | /* 2^50 - 2^10 */ for _ in 1..<10 { fe25519_square(&t, t) } 377 | /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0, t, z2_10_0) 378 | 379 | /* 2^51 - 2^1 */ fe25519_square(&t, z2_50_0) 380 | /* 2^100 - 2^50 */ for _ in 1..<50 { fe25519_square(&t, t) } 381 | /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, t, z2_50_0) 382 | 383 | /* 2^101 - 2^1 */ fe25519_square(&t, z2_100_0) 384 | /* 2^200 - 2^100 */ for _ in 1..<100 { fe25519_square(&t, t) } 385 | /* 2^200 - 2^0 */ fe25519_mul(&t, t, z2_100_0) 386 | 387 | /* 2^201 - 2^1 */ fe25519_square(&t, t) 388 | /* 2^250 - 2^50 */ for _ in 1..<50 { fe25519_square(&t, t) } 389 | /* 2^250 - 2^0 */ fe25519_mul(&t, t, z2_50_0) 390 | 391 | /* 2^251 - 2^1 */ fe25519_square(&t, t) 392 | /* 2^252 - 2^2 */ fe25519_square(&t, t) 393 | /* 2^252 - 3 */ fe25519_mul(&r, t, x) 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/RIPEMD160.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RIPEMD160.swift 3 | // 4 | // Created by Mitch Lang on 5/3/19. 5 | // Source: https://stackoverflow.com/questions/43091858/swift-hash-a-string-using-hash-hmac-with-ripemd160/43191938 6 | // 7 | 8 | import Foundation 9 | 10 | struct RIPEMD160 { 11 | 12 | private var MDbuf: (UInt32, UInt32, UInt32, UInt32, UInt32) 13 | private var buffer: Data 14 | private var count: Int64 // Total # of bytes processed. 15 | 16 | init() { 17 | MDbuf = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) 18 | buffer = Data() 19 | count = 0 20 | } 21 | 22 | private mutating func compress(_ X: UnsafePointer) { 23 | 24 | // *** Helper functions (originally macros in rmd160.h) *** 25 | 26 | /* ROL(x, n) cyclically rotates x over n bits to the left */ 27 | /* x must be of an unsigned 32 bits type and 0 <= n < 32. */ 28 | func ROL(_ x: UInt32, _ n: UInt32) -> UInt32 { 29 | return (x << n) | ( x >> (32 - n)) 30 | } 31 | 32 | /* the five basic functions F(), G() and H() */ 33 | 34 | func F(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 { 35 | return x ^ y ^ z 36 | } 37 | 38 | func G(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 { 39 | return (x & y) | (~x & z) 40 | } 41 | 42 | func H(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 { 43 | return (x | ~y) ^ z 44 | } 45 | 46 | func I(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 { 47 | return (x & z) | (y & ~z) 48 | } 49 | 50 | func J(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 { 51 | return x ^ (y | ~z) 52 | } 53 | 54 | /* the ten basic operations FF() through III() */ 55 | 56 | func FF(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 57 | a = a &+ F(b, c, d) &+ x 58 | a = ROL(a, s) &+ e 59 | c = ROL(c, 10) 60 | } 61 | 62 | func GG(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 63 | a = a &+ G(b, c, d) &+ x &+ 0x5a827999 64 | a = ROL(a, s) &+ e 65 | c = ROL(c, 10) 66 | } 67 | 68 | func HH(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 69 | a = a &+ H(b, c, d) &+ x &+ 0x6ed9eba1 70 | a = ROL(a, s) &+ e 71 | c = ROL(c, 10) 72 | } 73 | 74 | func II(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 75 | a = a &+ I(b, c, d) &+ x &+ 0x8f1bbcdc 76 | a = ROL(a, s) &+ e 77 | c = ROL(c, 10) 78 | } 79 | 80 | func JJ(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 81 | a = a &+ J(b, c, d) &+ x &+ 0xa953fd4e 82 | a = ROL(a, s) &+ e 83 | c = ROL(c, 10) 84 | } 85 | 86 | func FFF(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 87 | a = a &+ F(b, c, d) &+ x 88 | a = ROL(a, s) &+ e 89 | c = ROL(c, 10) 90 | } 91 | 92 | func GGG(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 93 | a = a &+ G(b, c, d) &+ x &+ 0x7a6d76e9 94 | a = ROL(a, s) &+ e 95 | c = ROL(c, 10) 96 | } 97 | 98 | func HHH(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 99 | a = a &+ H(b, c, d) &+ x &+ 0x6d703ef3 100 | a = ROL(a, s) &+ e 101 | c = ROL(c, 10) 102 | } 103 | 104 | func III(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 105 | a = a &+ I(b, c, d) &+ x &+ 0x5c4dd124 106 | a = ROL(a, s) &+ e 107 | c = ROL(c, 10) 108 | } 109 | 110 | func JJJ(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) { 111 | a = a &+ J(b, c, d) &+ x &+ 0x50a28be6 112 | a = ROL(a, s) &+ e 113 | c = ROL(c, 10) 114 | } 115 | 116 | // *** The function starts here *** 117 | 118 | var (aa, bb, cc, dd, ee) = MDbuf 119 | var (aaa, bbb, ccc, ddd, eee) = MDbuf 120 | 121 | /* round 1 */ 122 | FF(&aa, bb, &cc, dd, ee, X[ 0], 11) 123 | FF(&ee, aa, &bb, cc, dd, X[ 1], 14) 124 | FF(&dd, ee, &aa, bb, cc, X[ 2], 15) 125 | FF(&cc, dd, &ee, aa, bb, X[ 3], 12) 126 | FF(&bb, cc, &dd, ee, aa, X[ 4], 5) 127 | FF(&aa, bb, &cc, dd, ee, X[ 5], 8) 128 | FF(&ee, aa, &bb, cc, dd, X[ 6], 7) 129 | FF(&dd, ee, &aa, bb, cc, X[ 7], 9) 130 | FF(&cc, dd, &ee, aa, bb, X[ 8], 11) 131 | FF(&bb, cc, &dd, ee, aa, X[ 9], 13) 132 | FF(&aa, bb, &cc, dd, ee, X[10], 14) 133 | FF(&ee, aa, &bb, cc, dd, X[11], 15) 134 | FF(&dd, ee, &aa, bb, cc, X[12], 6) 135 | FF(&cc, dd, &ee, aa, bb, X[13], 7) 136 | FF(&bb, cc, &dd, ee, aa, X[14], 9) 137 | FF(&aa, bb, &cc, dd, ee, X[15], 8) 138 | 139 | /* round 2 */ 140 | GG(&ee, aa, &bb, cc, dd, X[ 7], 7) 141 | GG(&dd, ee, &aa, bb, cc, X[ 4], 6) 142 | GG(&cc, dd, &ee, aa, bb, X[13], 8) 143 | GG(&bb, cc, &dd, ee, aa, X[ 1], 13) 144 | GG(&aa, bb, &cc, dd, ee, X[10], 11) 145 | GG(&ee, aa, &bb, cc, dd, X[ 6], 9) 146 | GG(&dd, ee, &aa, bb, cc, X[15], 7) 147 | GG(&cc, dd, &ee, aa, bb, X[ 3], 15) 148 | GG(&bb, cc, &dd, ee, aa, X[12], 7) 149 | GG(&aa, bb, &cc, dd, ee, X[ 0], 12) 150 | GG(&ee, aa, &bb, cc, dd, X[ 9], 15) 151 | GG(&dd, ee, &aa, bb, cc, X[ 5], 9) 152 | GG(&cc, dd, &ee, aa, bb, X[ 2], 11) 153 | GG(&bb, cc, &dd, ee, aa, X[14], 7) 154 | GG(&aa, bb, &cc, dd, ee, X[11], 13) 155 | GG(&ee, aa, &bb, cc, dd, X[ 8], 12) 156 | 157 | /* round 3 */ 158 | HH(&dd, ee, &aa, bb, cc, X[ 3], 11) 159 | HH(&cc, dd, &ee, aa, bb, X[10], 13) 160 | HH(&bb, cc, &dd, ee, aa, X[14], 6) 161 | HH(&aa, bb, &cc, dd, ee, X[ 4], 7) 162 | HH(&ee, aa, &bb, cc, dd, X[ 9], 14) 163 | HH(&dd, ee, &aa, bb, cc, X[15], 9) 164 | HH(&cc, dd, &ee, aa, bb, X[ 8], 13) 165 | HH(&bb, cc, &dd, ee, aa, X[ 1], 15) 166 | HH(&aa, bb, &cc, dd, ee, X[ 2], 14) 167 | HH(&ee, aa, &bb, cc, dd, X[ 7], 8) 168 | HH(&dd, ee, &aa, bb, cc, X[ 0], 13) 169 | HH(&cc, dd, &ee, aa, bb, X[ 6], 6) 170 | HH(&bb, cc, &dd, ee, aa, X[13], 5) 171 | HH(&aa, bb, &cc, dd, ee, X[11], 12) 172 | HH(&ee, aa, &bb, cc, dd, X[ 5], 7) 173 | HH(&dd, ee, &aa, bb, cc, X[12], 5) 174 | 175 | /* round 4 */ 176 | II(&cc, dd, &ee, aa, bb, X[ 1], 11) 177 | II(&bb, cc, &dd, ee, aa, X[ 9], 12) 178 | II(&aa, bb, &cc, dd, ee, X[11], 14) 179 | II(&ee, aa, &bb, cc, dd, X[10], 15) 180 | II(&dd, ee, &aa, bb, cc, X[ 0], 14) 181 | II(&cc, dd, &ee, aa, bb, X[ 8], 15) 182 | II(&bb, cc, &dd, ee, aa, X[12], 9) 183 | II(&aa, bb, &cc, dd, ee, X[ 4], 8) 184 | II(&ee, aa, &bb, cc, dd, X[13], 9) 185 | II(&dd, ee, &aa, bb, cc, X[ 3], 14) 186 | II(&cc, dd, &ee, aa, bb, X[ 7], 5) 187 | II(&bb, cc, &dd, ee, aa, X[15], 6) 188 | II(&aa, bb, &cc, dd, ee, X[14], 8) 189 | II(&ee, aa, &bb, cc, dd, X[ 5], 6) 190 | II(&dd, ee, &aa, bb, cc, X[ 6], 5) 191 | II(&cc, dd, &ee, aa, bb, X[ 2], 12) 192 | 193 | /* round 5 */ 194 | JJ(&bb, cc, &dd, ee, aa, X[ 4], 9) 195 | JJ(&aa, bb, &cc, dd, ee, X[ 0], 15) 196 | JJ(&ee, aa, &bb, cc, dd, X[ 5], 5) 197 | JJ(&dd, ee, &aa, bb, cc, X[ 9], 11) 198 | JJ(&cc, dd, &ee, aa, bb, X[ 7], 6) 199 | JJ(&bb, cc, &dd, ee, aa, X[12], 8) 200 | JJ(&aa, bb, &cc, dd, ee, X[ 2], 13) 201 | JJ(&ee, aa, &bb, cc, dd, X[10], 12) 202 | JJ(&dd, ee, &aa, bb, cc, X[14], 5) 203 | JJ(&cc, dd, &ee, aa, bb, X[ 1], 12) 204 | JJ(&bb, cc, &dd, ee, aa, X[ 3], 13) 205 | JJ(&aa, bb, &cc, dd, ee, X[ 8], 14) 206 | JJ(&ee, aa, &bb, cc, dd, X[11], 11) 207 | JJ(&dd, ee, &aa, bb, cc, X[ 6], 8) 208 | JJ(&cc, dd, &ee, aa, bb, X[15], 5) 209 | JJ(&bb, cc, &dd, ee, aa, X[13], 6) 210 | 211 | /* parallel round 1 */ 212 | JJJ(&aaa, bbb, &ccc, ddd, eee, X[ 5], 8) 213 | JJJ(&eee, aaa, &bbb, ccc, ddd, X[14], 9) 214 | JJJ(&ddd, eee, &aaa, bbb, ccc, X[ 7], 9) 215 | JJJ(&ccc, ddd, &eee, aaa, bbb, X[ 0], 11) 216 | JJJ(&bbb, ccc, &ddd, eee, aaa, X[ 9], 13) 217 | JJJ(&aaa, bbb, &ccc, ddd, eee, X[ 2], 15) 218 | JJJ(&eee, aaa, &bbb, ccc, ddd, X[11], 15) 219 | JJJ(&ddd, eee, &aaa, bbb, ccc, X[ 4], 5) 220 | JJJ(&ccc, ddd, &eee, aaa, bbb, X[13], 7) 221 | JJJ(&bbb, ccc, &ddd, eee, aaa, X[ 6], 7) 222 | JJJ(&aaa, bbb, &ccc, ddd, eee, X[15], 8) 223 | JJJ(&eee, aaa, &bbb, ccc, ddd, X[ 8], 11) 224 | JJJ(&ddd, eee, &aaa, bbb, ccc, X[ 1], 14) 225 | JJJ(&ccc, ddd, &eee, aaa, bbb, X[10], 14) 226 | JJJ(&bbb, ccc, &ddd, eee, aaa, X[ 3], 12) 227 | JJJ(&aaa, bbb, &ccc, ddd, eee, X[12], 6) 228 | 229 | /* parallel round 2 */ 230 | III(&eee, aaa, &bbb, ccc, ddd, X[ 6], 9) 231 | III(&ddd, eee, &aaa, bbb, ccc, X[11], 13) 232 | III(&ccc, ddd, &eee, aaa, bbb, X[ 3], 15) 233 | III(&bbb, ccc, &ddd, eee, aaa, X[ 7], 7) 234 | III(&aaa, bbb, &ccc, ddd, eee, X[ 0], 12) 235 | III(&eee, aaa, &bbb, ccc, ddd, X[13], 8) 236 | III(&ddd, eee, &aaa, bbb, ccc, X[ 5], 9) 237 | III(&ccc, ddd, &eee, aaa, bbb, X[10], 11) 238 | III(&bbb, ccc, &ddd, eee, aaa, X[14], 7) 239 | III(&aaa, bbb, &ccc, ddd, eee, X[15], 7) 240 | III(&eee, aaa, &bbb, ccc, ddd, X[ 8], 12) 241 | III(&ddd, eee, &aaa, bbb, ccc, X[12], 7) 242 | III(&ccc, ddd, &eee, aaa, bbb, X[ 4], 6) 243 | III(&bbb, ccc, &ddd, eee, aaa, X[ 9], 15) 244 | III(&aaa, bbb, &ccc, ddd, eee, X[ 1], 13) 245 | III(&eee, aaa, &bbb, ccc, ddd, X[ 2], 11) 246 | 247 | /* parallel round 3 */ 248 | HHH(&ddd, eee, &aaa, bbb, ccc, X[15], 9) 249 | HHH(&ccc, ddd, &eee, aaa, bbb, X[ 5], 7) 250 | HHH(&bbb, ccc, &ddd, eee, aaa, X[ 1], 15) 251 | HHH(&aaa, bbb, &ccc, ddd, eee, X[ 3], 11) 252 | HHH(&eee, aaa, &bbb, ccc, ddd, X[ 7], 8) 253 | HHH(&ddd, eee, &aaa, bbb, ccc, X[14], 6) 254 | HHH(&ccc, ddd, &eee, aaa, bbb, X[ 6], 6) 255 | HHH(&bbb, ccc, &ddd, eee, aaa, X[ 9], 14) 256 | HHH(&aaa, bbb, &ccc, ddd, eee, X[11], 12) 257 | HHH(&eee, aaa, &bbb, ccc, ddd, X[ 8], 13) 258 | HHH(&ddd, eee, &aaa, bbb, ccc, X[12], 5) 259 | HHH(&ccc, ddd, &eee, aaa, bbb, X[ 2], 14) 260 | HHH(&bbb, ccc, &ddd, eee, aaa, X[10], 13) 261 | HHH(&aaa, bbb, &ccc, ddd, eee, X[ 0], 13) 262 | HHH(&eee, aaa, &bbb, ccc, ddd, X[ 4], 7) 263 | HHH(&ddd, eee, &aaa, bbb, ccc, X[13], 5) 264 | 265 | /* parallel round 4 */ 266 | GGG(&ccc, ddd, &eee, aaa, bbb, X[ 8], 15) 267 | GGG(&bbb, ccc, &ddd, eee, aaa, X[ 6], 5) 268 | GGG(&aaa, bbb, &ccc, ddd, eee, X[ 4], 8) 269 | GGG(&eee, aaa, &bbb, ccc, ddd, X[ 1], 11) 270 | GGG(&ddd, eee, &aaa, bbb, ccc, X[ 3], 14) 271 | GGG(&ccc, ddd, &eee, aaa, bbb, X[11], 14) 272 | GGG(&bbb, ccc, &ddd, eee, aaa, X[15], 6) 273 | GGG(&aaa, bbb, &ccc, ddd, eee, X[ 0], 14) 274 | GGG(&eee, aaa, &bbb, ccc, ddd, X[ 5], 6) 275 | GGG(&ddd, eee, &aaa, bbb, ccc, X[12], 9) 276 | GGG(&ccc, ddd, &eee, aaa, bbb, X[ 2], 12) 277 | GGG(&bbb, ccc, &ddd, eee, aaa, X[13], 9) 278 | GGG(&aaa, bbb, &ccc, ddd, eee, X[ 9], 12) 279 | GGG(&eee, aaa, &bbb, ccc, ddd, X[ 7], 5) 280 | GGG(&ddd, eee, &aaa, bbb, ccc, X[10], 15) 281 | GGG(&ccc, ddd, &eee, aaa, bbb, X[14], 8) 282 | 283 | /* parallel round 5 */ 284 | FFF(&bbb, ccc, &ddd, eee, aaa, X[12] , 8) 285 | FFF(&aaa, bbb, &ccc, ddd, eee, X[15] , 5) 286 | FFF(&eee, aaa, &bbb, ccc, ddd, X[10] , 12) 287 | FFF(&ddd, eee, &aaa, bbb, ccc, X[ 4] , 9) 288 | FFF(&ccc, ddd, &eee, aaa, bbb, X[ 1] , 12) 289 | FFF(&bbb, ccc, &ddd, eee, aaa, X[ 5] , 5) 290 | FFF(&aaa, bbb, &ccc, ddd, eee, X[ 8] , 14) 291 | FFF(&eee, aaa, &bbb, ccc, ddd, X[ 7] , 6) 292 | FFF(&ddd, eee, &aaa, bbb, ccc, X[ 6] , 8) 293 | FFF(&ccc, ddd, &eee, aaa, bbb, X[ 2] , 13) 294 | FFF(&bbb, ccc, &ddd, eee, aaa, X[13] , 6) 295 | FFF(&aaa, bbb, &ccc, ddd, eee, X[14] , 5) 296 | FFF(&eee, aaa, &bbb, ccc, ddd, X[ 0] , 15) 297 | FFF(&ddd, eee, &aaa, bbb, ccc, X[ 3] , 13) 298 | FFF(&ccc, ddd, &eee, aaa, bbb, X[ 9] , 11) 299 | FFF(&bbb, ccc, &ddd, eee, aaa, X[11] , 11) 300 | 301 | /* combine results */ 302 | MDbuf = (MDbuf.1 &+ cc &+ ddd, 303 | MDbuf.2 &+ dd &+ eee, 304 | MDbuf.3 &+ ee &+ aaa, 305 | MDbuf.4 &+ aa &+ bbb, 306 | MDbuf.0 &+ bb &+ ccc) 307 | } 308 | 309 | mutating func update(data: Data) { 310 | var X = [UInt32](repeating: 0, count: 16) 311 | var pos = data.startIndex 312 | var length = data.count 313 | 314 | // Process remaining bytes from last call: 315 | if buffer.count > 0 && buffer.count + length >= 64 { 316 | let amount = 64 - buffer.count 317 | buffer.append(data[..= 64 { 328 | X.withUnsafeMutableBytes { 329 | _ = data[pos.. Data { 342 | var X = [UInt32](repeating: 0, count: 16) 343 | /* append the bit m_n == 1 */ 344 | buffer.append(0x80) 345 | X.withUnsafeMutableBytes { 346 | _ = buffer.copyBytes(to: $0) 347 | } 348 | 349 | if (count & 63) > 55 { 350 | /* length goes to next block */ 351 | compress(X) 352 | X = [UInt32](repeating: 0, count: 16) 353 | } 354 | 355 | /* append length in bits */ 356 | let lswlen = UInt32(truncatingIfNeeded: count) 357 | let mswlen = UInt32(UInt64(count) >> 32) 358 | X[14] = lswlen << 3 359 | X[15] = (lswlen >> 29) | (mswlen << 3) 360 | compress(X) 361 | 362 | buffer = Data() 363 | let result = [MDbuf.0, MDbuf.1, MDbuf.2, MDbuf.3, MDbuf.4] 364 | return result.withUnsafeBytes { Data($0) } 365 | } 366 | } 367 | 368 | extension RIPEMD160 { 369 | 370 | static func hash(message: Data) -> Data { 371 | var md = RIPEMD160() 372 | md.update(data: message) 373 | return md.finalize() 374 | } 375 | 376 | static func hash(message: String) -> Data { 377 | return RIPEMD160.hash(message: message.data(using: .utf8)!) 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/Serializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Serializer.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/6/19. 6 | // 7 | // reference: https://github.com/ripple/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/serialize.py 8 | // 9 | 10 | import Foundation 11 | 12 | private struct Definitions { 13 | 14 | var TYPES: [String: Int] 15 | var LEDGER_ENTRY_TYPES: [String : Int] 16 | var FIELDS: [String:FieldInfo] 17 | var TRANSACTION_RESULTS: [String : Int] 18 | var TRANSACTION_TYPES: [String : Int] 19 | 20 | init(dict: [String:AnyObject]) { 21 | self.TYPES = dict["TYPES"] as! [String:Int] 22 | self.LEDGER_ENTRY_TYPES = dict["LEDGER_ENTRY_TYPES"] as! [String:Int] 23 | self.TRANSACTION_RESULTS = dict["TRANSACTION_RESULTS"] as! [String:Int] 24 | self.TRANSACTION_TYPES = dict["TRANSACTION_TYPES"] as! [String:Int] 25 | 26 | let fields = dict["FIELDS"] as! [[AnyObject]] 27 | var fieldsDict: [String:FieldInfo] = [:] 28 | _ = fields.map { (array) in 29 | let field = array[0] as! String 30 | let fieldInfo = FieldInfo(dict: array[1] as! NSDictionary) 31 | fieldsDict[field] = fieldInfo 32 | } 33 | self.FIELDS = fieldsDict 34 | } 35 | } 36 | 37 | private struct FieldOrder { 38 | var name: String 39 | var orderTuple: OrderTuple 40 | } 41 | 42 | private struct OrderTuple { 43 | var typeCode: Int 44 | var order: Int 45 | } 46 | 47 | private struct TypeWrapper { 48 | var type: String 49 | var object: [String:Any] 50 | } 51 | 52 | private struct FieldInfo { 53 | var nth: Int 54 | var isVLEncoded: Bool 55 | var isSerialized: Bool 56 | var isSigningField: Bool 57 | var type: String 58 | 59 | init(dict: NSDictionary) { 60 | self.nth = dict["nth"] as! Int 61 | self.isVLEncoded = dict["isVLEncoded"] as! Bool 62 | self.isSerialized = dict["isSerialized"] as! Bool 63 | self.isSigningField = dict["isSigningField"] as! Bool 64 | self.type = dict["type"] as! String 65 | 66 | } 67 | } 68 | 69 | class Serializer { 70 | 71 | // instance variables 72 | private var definitions: Definitions! 73 | 74 | init() { 75 | do { 76 | let data: Data = serializerDefinitions.data(using: .utf8)! 77 | let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) 78 | if let jsonResult = jsonResult as? [String:AnyObject] { 79 | self.definitions = Definitions(dict: jsonResult) 80 | } 81 | } catch { 82 | print(error.localizedDescription) 83 | } 84 | } 85 | 86 | private func fieldSortKey(fieldName: String) -> OrderTuple { 87 | // Return a tuple sort key for a given field name 88 | let fieldTypeName = definitions.FIELDS[fieldName]!.type 89 | let tuple = OrderTuple(typeCode: definitions.TYPES[fieldTypeName]!, order: definitions.FIELDS[fieldName]!.nth) 90 | return tuple 91 | } 92 | 93 | private func fieldID(fieldName: String) -> Data { 94 | 95 | /* 96 | Returns the unique field ID for a given field name. 97 | This field ID consists of the type code and field code, in 1 to 3 bytes 98 | depending on whether those values are "common" (<16) or "uncommon" (>=16) 99 | */ 100 | let fieldTypeName = definitions.FIELDS[fieldName]!.type 101 | let typeCode = definitions.TYPES[fieldTypeName]! 102 | let fieldCode = definitions.FIELDS[fieldName]!.nth 103 | 104 | // Codes must be nonzero and fit in 1 byte 105 | assert(0 < fieldCode && fieldCode <= 255) 106 | assert(0 < typeCode && typeCode <= 255) 107 | 108 | if typeCode < 16 && fieldCode < 16 { 109 | // high 4 bits is the type_code 110 | // low 4 bits is the field code 111 | let combinedCode = (typeCode << 4) | fieldCode 112 | return UInt8Byte(combinedCode) 113 | } else if typeCode >= 16 && fieldCode < 16 { 114 | // first 4 bits are zeroes 115 | // next 4 bits is field code 116 | // next byte is type code 117 | let byte1 = UInt8Byte(fieldCode) 118 | let byte2 = UInt8Byte(typeCode) 119 | return byte1 + byte2 120 | } else if typeCode < 16 && fieldCode >= 16 { 121 | // first 4 bits is type code 122 | // next 4 bits are zeroes 123 | // next byte is field code 124 | let byte1 = UInt8Byte(typeCode << 4) 125 | let byte2 = UInt8Byte(fieldCode) 126 | return byte1 + byte2 127 | } else { 128 | // both are >= 16 129 | // first byte is all zeroes 130 | // second byte is type 131 | // third byte is field code 132 | let byte2 = UInt8Byte(typeCode) 133 | let byte3 = UInt8Byte(fieldCode) 134 | return Data([0x00]) + byte2 + byte3 135 | } 136 | } 137 | 138 | private func vlEncode(contents: Data) -> Data { 139 | /* 140 | Helper function for length-prefixed fields including Blob types 141 | and some AccountID types. 142 | Encodes arbitrary binary data with a length prefix. The length of the prefix 143 | is 1-3 bytes depending on the length of the contents: 144 | Content length <= 192 bytes: prefix is 1 byte 145 | 192 bytes < Content length <= 12480 bytes: prefix is 2 bytes 146 | 12480 bytes < Content length <= 918744 bytes: prefix is 3 bytes 147 | */ 148 | let contents = [UInt8](contents) 149 | var vlLength = contents.count 150 | if vlLength <= 192 { 151 | let lengthByte = Data([UInt8(vlLength)]) 152 | return lengthByte + contents 153 | } else if vlLength <= 12480 { 154 | vlLength -= 193 155 | let byte1 = UInt8Byte((vlLength >> 8) + 193) 156 | let byte2 = UInt8Byte(vlLength & 0xff) 157 | return byte1 + byte2 + contents 158 | } else if vlLength <= 918744 { 159 | vlLength -= 12481 160 | let byte1 = UInt8Byte(241 + (vlLength >> 16)) 161 | let byte2 = UInt8Byte((vlLength >> 8) & 0xff) 162 | let byte3 = UInt8Byte(vlLength & 0xff) 163 | return byte1 + byte2 + byte3 + contents 164 | } 165 | fatalError("VariableLength field must be <= 918744 bytes long") 166 | } 167 | 168 | private func decodeAddress(address: String) -> Data { 169 | let _array = [UInt8](Data(base58Decoding: address)!) 170 | let array = _array.prefix(_array.count-4) 171 | 172 | //FIXME: base58Decoding 173 | if (array[0] == 0 || array[0] == 114) && array.count == 21 { 174 | return Data(array.suffix(from: 1)) 175 | } else { 176 | fatalError() 177 | } 178 | } 179 | 180 | private func accountIDToBytes(address: String) -> Data { 181 | /* 182 | Serialize an AccountID field type. These are length-prefixed. 183 | Some fields contain nested non-length-prefixed AccountIDs directly; those 184 | call decode_address() instead of this function. 185 | */ 186 | let addressData = decodeAddress(address: address) 187 | return vlEncode(contents: addressData) 188 | } 189 | 190 | private func amountToBytes(amount: String) -> Data { 191 | /* 192 | Serializes an "Amount" type, which can be either XRP or an issued currency: 193 | - XRP: 64 bits; 0, followed by 1 ("is positive"), followed by 62 bit UInt amount 194 | - Issued Currency: 64 bits of amount, followed by 160 bit currency code and 195 | 160 bit issuer AccountID. 196 | */ 197 | var xrpAmount = Int64(amount)! 198 | let _edge: Int64 = 100000000000000000 //10^17 199 | if (xrpAmount >= 0) { 200 | assert(xrpAmount <= _edge) 201 | // set the "is positive" bit -- this is backwards from usual two's complement! 202 | let mask: Int64 = 0x4000000000000000 203 | xrpAmount = xrpAmount | mask 204 | } else { 205 | assert(xrpAmount >= -1*_edge) 206 | // convert to absolute value, leaving the "is positive" bit unset 207 | xrpAmount = -xrpAmount 208 | } 209 | return xrpAmount.bigEndian.data 210 | } 211 | 212 | private func amountDictToBytes(dict: [String:Any]) -> Data { 213 | if dict.keys.sorted() != ["currency", "issuer", "value"] { 214 | fatalError("amount must have currency, value, issuer") 215 | } 216 | 217 | let issuedAmount = IssuedAmount(value: dict["value"] as! String).canonicalize() 218 | let currencyCode = currencyCodeToBytes(codeString: dict["currency"] as! String) 219 | return issuedAmount + currencyCode + decodeAddress(address: dict["issuer"] as! String) 220 | } 221 | 222 | private func currencyCodeToBytes(codeString: String, xrpOkay:Bool = false) -> Data { 223 | //FIXME: regex is wacky 224 | let regex = try! NSRegularExpression(pattern: "^[A-Za-z0-9?!@#$%^&*<>(){}|]{3}$", options: []) 225 | let matches = regex.matches(in: codeString, options: [], range: NSMakeRange(0,codeString.count)) 226 | let regex2 = try! NSRegularExpression(pattern: "^[0-9a-fA-F]{40}$", options: []) 227 | let matches2 = regex2.matches(in: codeString, options: [], range: NSMakeRange(0,codeString.count)) 228 | if matches.count != 0 { 229 | if codeString == "XRP" { 230 | if xrpOkay { 231 | // Rare, but when the currency code "XRP" is serialized, it's 232 | // a special-case all zeroes. 233 | return Data(repeating: 0, count: 20) 234 | 235 | } 236 | } 237 | 238 | let ascii = codeString.data(using: .nonLossyASCII)! 239 | // standard currency codes: https://developers.ripple.com/currency-formats.html#standard-currency-codes 240 | // 8 bits type code (0x00) 241 | // 88 bits reserved (0's) 242 | // 24 bits ASCII 243 | // 16 bits version (0x00) 244 | // 24 bits reserved (0's) 245 | return Data(repeating: 0, count: 12) + ascii + Data(repeating: 0, count: 5) 246 | } else if matches2.count != 0 { 247 | return Data(hex: codeString) 248 | } 249 | 250 | fatalError("invalid currency") 251 | } 252 | 253 | private func pathsetToBytes(pathset: [[[String:Any]]]) -> Data { 254 | /* 255 | Serialize a PathSet, which is an array of arrays, 256 | where each inner array represents one possible payment path. 257 | A path consists of "path step" objects in sequence, each with one or 258 | more of "account", "currency", and "issuer" fields, plus (ignored) "type" 259 | and "type_hex" fields which indicate which fields are present. 260 | (We re-create the type field for serialization based on which of the core 261 | 3 fields are present.) 262 | */ 263 | 264 | if pathset.count == 0 { 265 | fatalError("PathSet type must not be empty") 266 | } 267 | 268 | var pathSetBytes: Data = Data() 269 | for (index, path) in pathset.enumerated() { 270 | let _pathAsBytes = pathAsBytes(path: path) 271 | pathSetBytes.append(_pathAsBytes) 272 | if index == pathset.count - 1 { 273 | pathSetBytes.append(Data(hex: "00")) 274 | } else { 275 | pathSetBytes.append(Data(hex: "ff")) 276 | } 277 | } 278 | return pathSetBytes 279 | } 280 | 281 | private func pathAsBytes(path: [[String:Any]]) -> Data { 282 | // Helper function for representing one member of a pathset as a bytes object 283 | if path.count == 0 { 284 | fatalError("Path type must not be empty") 285 | } 286 | 287 | var pathBytes = Data() 288 | for step in path { 289 | var stepData = Data() 290 | var typeByte: UInt8 = 0 291 | if step.keys.contains("account") { 292 | typeByte |= 0x01 293 | stepData.append(decodeAddress(address: step["account"] as! String)) 294 | } 295 | if step.keys.contains("currency") { 296 | typeByte |= 0x10 297 | stepData.append(currencyCodeToBytes(codeString: step["currency"] as! String, xrpOkay: true)) 298 | } 299 | if step.keys.contains("issuer") { 300 | typeByte |= 0x20 301 | stepData.append(decodeAddress(address: step["issuer"] as! String)) 302 | } 303 | stepData = [typeByte] + stepData 304 | pathBytes.append(stepData) 305 | } 306 | 307 | return pathBytes 308 | } 309 | 310 | private func arrayToBytes(array: [TypeWrapper]) -> Data { 311 | /* 312 | Serialize an array of objects from decoded JSON. 313 | Each member object must have a type wrapper and an inner object. 314 | For example: 315 | [ 316 | { 317 | // wrapper object 318 | "Memo": { 319 | // inner object 320 | "MemoType": "687474703a2f2f6578616d706c652e636f6d2f6d656d6f2f67656e65726963", 321 | "MemoData": "72656e74" 322 | } 323 | } 324 | ] 325 | */ 326 | var membersAsBytes: [Data] = [] 327 | for el in array { 328 | let wrapperKey = el.type 329 | let innerObject = el.object 330 | membersAsBytes.append(fieldToBytes(fieldName: wrapperKey, fieldVal: innerObject)) 331 | } 332 | membersAsBytes.append(fieldID(fieldName: "ArrayEndMarker")) 333 | return membersAsBytes.reduce(Data(), { (result, newData) -> Data in 334 | return result + newData 335 | }) 336 | } 337 | 338 | private func blobToBytes(hexBlob: String) -> Data { 339 | /* 340 | Serializes a string of hex as binary data with a length prefix. 341 | */ 342 | return vlEncode(contents: hexBlob.hexadecimal ?? Data()) 343 | } 344 | 345 | private func currencyCodeToBytes(code: String) -> Data { 346 | fatalError("currencyCodeToBytes not implemented") 347 | } 348 | 349 | private func hash128ToBytes(hexString: String) -> Data { 350 | // Serializes a hexadecimal string as binary and confirms that it's 128 bits 351 | let data = hashToBytes(hexString: hexString) 352 | if data.count != 16 { 353 | fatalError("hash128 is not 128 bits long") 354 | } 355 | return data 356 | } 357 | 358 | private func hash160ToBytes(hexString: String) -> Data { 359 | let data = hashToBytes(hexString: hexString) 360 | if data.count != 20 { 361 | fatalError("hash160 is not 160 bits long") 362 | } 363 | return data 364 | } 365 | 366 | private func hash256ToBytes(hexString: String) -> Data { 367 | let data = hashToBytes(hexString: hexString) 368 | if data.count != 32 { 369 | fatalError("hash256 is not 256 bits long") 370 | } 371 | return data 372 | } 373 | 374 | private func hashToBytes(hexString: String) -> Data { 375 | return hexString.hexadecimal! 376 | } 377 | 378 | private func objectToBytes(wrapper: TypeWrapper) -> Data { 379 | let innerObject = wrapper.object 380 | let tuples = innerObject.keys.map { (key) -> FieldOrder in 381 | let tuple = self.fieldSortKey(fieldName: key) 382 | return FieldOrder(name: key, orderTuple: tuple) 383 | } 384 | let sortedTuples = tuples.sorted { (lh, rh) -> Bool in 385 | if lh.orderTuple.typeCode == rh.orderTuple.typeCode { 386 | return lh.orderTuple.order < rh.orderTuple.order 387 | } else { 388 | return lh.orderTuple.typeCode < rh.orderTuple.typeCode 389 | } 390 | } 391 | var fieldAsBytes: [Data] = [] 392 | for tuple in sortedTuples { 393 | if definitions.FIELDS[tuple.name]!.isSerialized { 394 | let fieldVal = innerObject[tuple.name]! 395 | let bytes = fieldToBytes(fieldName: tuple.name, fieldVal: fieldVal) 396 | fieldAsBytes.append(bytes) 397 | } 398 | } 399 | fieldAsBytes.append(fieldID(fieldName: "ObjectEndMarker")) 400 | return fieldAsBytes.reduce(Data(), { (result, newData) -> Data in 401 | return result + newData 402 | }) 403 | } 404 | 405 | private func txTypeToBytes(type: String) -> Data { 406 | let type = UInt16(definitions.TRANSACTION_TYPES[type]!) 407 | return UInt16Bytes(type) 408 | } 409 | 410 | private func UInt8Byte(_ int: Int) -> Data { 411 | return Data([UInt8(int)]) 412 | } 413 | 414 | private func UInt8Byte(_ int: UInt8) -> Data { 415 | return int.bigEndian.data 416 | } 417 | 418 | private func UInt16Bytes(_ int: UInt16) -> Data { 419 | return int.bigEndian.data 420 | } 421 | 422 | private func UInt32Bytes(_ int: UInt32) -> Data { 423 | return int.bigEndian.data 424 | } 425 | 426 | // ======================== 427 | // Core serialization logic 428 | // ======================== 429 | 430 | private func fieldToBytes(fieldName: String, fieldVal: Any) -> Data { 431 | 432 | let fieldType = definitions.FIELDS[fieldName]!.type 433 | let idPrefix = fieldID(fieldName: fieldName) 434 | 435 | // special case 436 | if fieldName == "TransactionType" { 437 | return idPrefix + txTypeToBytes(type: fieldVal as! String) 438 | } 439 | 440 | let dispatch = { (fieldType: String, fieldVal: Any) -> Data in 441 | switch fieldType { 442 | case "AccountID": 443 | let address = fieldVal as! String 444 | return self.accountIDToBytes(address: address) 445 | case "Amount": 446 | if let amount = fieldVal as? String { 447 | return self.amountToBytes(amount: amount) 448 | } else if let amount = fieldVal as? [String:Any] { 449 | return self.amountDictToBytes(dict: amount) 450 | } 451 | fatalError() 452 | case "Blob": 453 | let hexBlob = fieldVal as! String 454 | return self.blobToBytes(hexBlob: hexBlob) 455 | case "Hash128": 456 | let hexString = fieldVal as! String 457 | return self.hash128ToBytes(hexString: hexString) 458 | case "Hash160": 459 | let hexString = fieldVal as! String 460 | return self.hash160ToBytes(hexString: hexString) 461 | case "Hash256": 462 | let hexString = fieldVal as! String 463 | return self.hash256ToBytes(hexString: hexString) 464 | case "PathSet": 465 | let pathSet = fieldVal as! [[[String:Any]]] 466 | return self.pathsetToBytes(pathset: pathSet) 467 | case "STArray": 468 | let array = fieldVal as! [[String:Any]] 469 | let wrappers: [TypeWrapper] = array.map({ (dict) -> TypeWrapper in 470 | let kv = dict.first! 471 | let body = dict 472 | return TypeWrapper(type: kv.key, object: body) 473 | }) 474 | return self.arrayToBytes(array: wrappers) 475 | case "STObject": 476 | let dict = fieldVal as! [String:Any] 477 | let kv = dict.first! 478 | let body = kv.value as! [String:Any] 479 | let wrapper = TypeWrapper(type: kv.key, object: body) 480 | return self.objectToBytes(wrapper: wrapper) 481 | case "UInt8": 482 | let int = fieldVal as! UInt8 483 | return self.UInt8Byte(int) 484 | case "UInt16": 485 | let int = fieldVal as! UInt16 486 | return self.UInt16Bytes(int) 487 | case "UInt32": 488 | let int = fieldVal as! UInt32 489 | return self.UInt32Bytes(int) 490 | default: 491 | fatalError("Invalid field name") 492 | } 493 | } 494 | 495 | let fieldBinary = dispatch(fieldType, fieldVal) 496 | return idPrefix + fieldBinary 497 | 498 | } 499 | 500 | public func serializeTx(tx: [String:Any], forSigning: Bool = false) -> Data { 501 | /* 502 | Takes a transaction as decoded JSON and returns a bytes object representing 503 | the transaction in binary format. 504 | The input format should omit transaction metadata and the transaction 505 | should be formatted with the transaction instructions at the top level. 506 | ("hash" can be included, but will be ignored) 507 | If for_signing=True, then only signing fields are serialized, so you can use 508 | the output to sign the transaction. 509 | SigningPubKey and TxnSignature are optional, but the transaction can't 510 | be submitted without them. 511 | For example: 512 | { 513 | "TransactionType" : "Payment", 514 | "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 515 | "Destination" : "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", 516 | "Amount" : { 517 | "currency" : "USD", 518 | "value" : "1", 519 | "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" 520 | }, 521 | "Fee": "12", 522 | "Flags": 2147483648, 523 | "Sequence": 2 524 | } 525 | */ 526 | 527 | let tuples = tx.keys.map { (key) -> FieldOrder in 528 | let tuple = self.fieldSortKey(fieldName: key) 529 | return FieldOrder(name: key, orderTuple: tuple) 530 | } 531 | let sortedTuples = tuples.sorted { (lh, rh) -> Bool in 532 | if lh.orderTuple.typeCode == rh.orderTuple.typeCode { 533 | return lh.orderTuple.order < rh.orderTuple.order 534 | } else { 535 | return lh.orderTuple.typeCode < rh.orderTuple.typeCode 536 | } 537 | } 538 | var fieldAsBytes: [Data] = [] 539 | for tuple in sortedTuples { 540 | if definitions.FIELDS[tuple.name]!.isSerialized { 541 | if forSigning && !definitions.FIELDS[tuple.name]!.isSigningField { 542 | continue 543 | } 544 | let fieldVal = tx[tuple.name]! 545 | let bytes = fieldToBytes(fieldName: tuple.name, fieldVal: fieldVal) 546 | fieldAsBytes.append(bytes) 547 | // print(tuple.name) 548 | // printBytes([bytes]) 549 | 550 | } 551 | } 552 | return fieldAsBytes.reduce(Data(), { (result, newData) -> Data in 553 | return result + newData 554 | }) 555 | } 556 | 557 | private func printBytes(_ bytes: [Data]) { 558 | let combined = bytes.reduce(Data(), { (result, newData) -> Data in 559 | return result + newData 560 | }) 561 | print(combined.hexadecimal) 562 | print("\n") 563 | } 564 | 565 | } 566 | -------------------------------------------------------------------------------- /Sources/XRPKit/Utilities/SerializerDefinitions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerializerDefinitions.swift 3 | // XRPKit 4 | // 5 | // Created by Mitch Lang on 5/11/19. 6 | // 7 | 8 | import Foundation 9 | 10 | let serializerDefinitions = """ 11 | { 12 | "TYPES": { 13 | "Validation": 10003, 14 | "Done": -1, 15 | "Hash128": 4, 16 | "Blob": 7, 17 | "AccountID": 8, 18 | "Amount": 6, 19 | "Hash256": 5, 20 | "UInt8": 16, 21 | "Vector256": 19, 22 | "STObject": 14, 23 | "Unknown": -2, 24 | "Transaction": 10001, 25 | "Hash160": 17, 26 | "PathSet": 18, 27 | "LedgerEntry": 10002, 28 | "UInt16": 1, 29 | "NotPresent": 0, 30 | "UInt64": 3, 31 | "UInt32": 2, 32 | "STArray": 15 33 | }, 34 | "LEDGER_ENTRY_TYPES": { 35 | "Any": -3, 36 | "Child": -2, 37 | "Invalid": -1, 38 | "AccountRoot": 97, 39 | "DirectoryNode": 100, 40 | "RippleState": 114, 41 | "Ticket": 84, 42 | "SignerList": 83, 43 | "Offer": 111, 44 | "LedgerHashes": 104, 45 | "Amendments": 102, 46 | "FeeSettings": 115, 47 | "Escrow": 117, 48 | "PayChannel": 120, 49 | "DepositPreauth": 112, 50 | "Check": 67, 51 | "Nickname": 110, 52 | "Contract": 99, 53 | "GeneratorMap": 103 54 | }, 55 | "FIELDS": [ 56 | [ 57 | "Generic", 58 | { 59 | "nth": 0, 60 | "isVLEncoded": false, 61 | "isSerialized": false, 62 | "isSigningField": false, 63 | "type": "Unknown" 64 | } 65 | ], 66 | [ 67 | "Invalid", 68 | { 69 | "nth": -1, 70 | "isVLEncoded": false, 71 | "isSerialized": false, 72 | "isSigningField": false, 73 | "type": "Unknown" 74 | } 75 | ], 76 | [ 77 | "LedgerEntryType", 78 | { 79 | "nth": 1, 80 | "isVLEncoded": false, 81 | "isSerialized": true, 82 | "isSigningField": true, 83 | "type": "UInt16" 84 | } 85 | ], 86 | [ 87 | "TransactionType", 88 | { 89 | "nth": 2, 90 | "isVLEncoded": false, 91 | "isSerialized": true, 92 | "isSigningField": true, 93 | "type": "UInt16" 94 | } 95 | ], 96 | [ 97 | "SignerWeight", 98 | { 99 | "nth": 3, 100 | "isVLEncoded": false, 101 | "isSerialized": true, 102 | "isSigningField": true, 103 | "type": "UInt16" 104 | } 105 | ], 106 | [ 107 | "Flags", 108 | { 109 | "nth": 2, 110 | "isVLEncoded": false, 111 | "isSerialized": true, 112 | "isSigningField": true, 113 | "type": "UInt32" 114 | } 115 | ], 116 | [ 117 | "SourceTag", 118 | { 119 | "nth": 3, 120 | "isVLEncoded": false, 121 | "isSerialized": true, 122 | "isSigningField": true, 123 | "type": "UInt32" 124 | } 125 | ], 126 | [ 127 | "Sequence", 128 | { 129 | "nth": 4, 130 | "isVLEncoded": false, 131 | "isSerialized": true, 132 | "isSigningField": true, 133 | "type": "UInt32" 134 | } 135 | ], 136 | [ 137 | "PreviousTxnLgrSeq", 138 | { 139 | "nth": 5, 140 | "isVLEncoded": false, 141 | "isSerialized": true, 142 | "isSigningField": true, 143 | "type": "UInt32" 144 | } 145 | ], 146 | [ 147 | "LedgerSequence", 148 | { 149 | "nth": 6, 150 | "isVLEncoded": false, 151 | "isSerialized": true, 152 | "isSigningField": true, 153 | "type": "UInt32" 154 | } 155 | ], 156 | [ 157 | "CloseTime", 158 | { 159 | "nth": 7, 160 | "isVLEncoded": false, 161 | "isSerialized": true, 162 | "isSigningField": true, 163 | "type": "UInt32" 164 | } 165 | ], 166 | [ 167 | "ParentCloseTime", 168 | { 169 | "nth": 8, 170 | "isVLEncoded": false, 171 | "isSerialized": true, 172 | "isSigningField": true, 173 | "type": "UInt32" 174 | } 175 | ], 176 | [ 177 | "SigningTime", 178 | { 179 | "nth": 9, 180 | "isVLEncoded": false, 181 | "isSerialized": true, 182 | "isSigningField": true, 183 | "type": "UInt32" 184 | } 185 | ], 186 | [ 187 | "Expiration", 188 | { 189 | "nth": 10, 190 | "isVLEncoded": false, 191 | "isSerialized": true, 192 | "isSigningField": true, 193 | "type": "UInt32" 194 | } 195 | ], 196 | [ 197 | "TransferRate", 198 | { 199 | "nth": 11, 200 | "isVLEncoded": false, 201 | "isSerialized": true, 202 | "isSigningField": true, 203 | "type": "UInt32" 204 | } 205 | ], 206 | [ 207 | "WalletSize", 208 | { 209 | "nth": 12, 210 | "isVLEncoded": false, 211 | "isSerialized": true, 212 | "isSigningField": true, 213 | "type": "UInt32" 214 | } 215 | ], 216 | [ 217 | "OwnerCount", 218 | { 219 | "nth": 13, 220 | "isVLEncoded": false, 221 | "isSerialized": true, 222 | "isSigningField": true, 223 | "type": "UInt32" 224 | } 225 | ], 226 | [ 227 | "DestinationTag", 228 | { 229 | "nth": 14, 230 | "isVLEncoded": false, 231 | "isSerialized": true, 232 | "isSigningField": true, 233 | "type": "UInt32" 234 | } 235 | ], 236 | [ 237 | "HighQualityIn", 238 | { 239 | "nth": 16, 240 | "isVLEncoded": false, 241 | "isSerialized": true, 242 | "isSigningField": true, 243 | "type": "UInt32" 244 | } 245 | ], 246 | [ 247 | "HighQualityOut", 248 | { 249 | "nth": 17, 250 | "isVLEncoded": false, 251 | "isSerialized": true, 252 | "isSigningField": true, 253 | "type": "UInt32" 254 | } 255 | ], 256 | [ 257 | "LowQualityIn", 258 | { 259 | "nth": 18, 260 | "isVLEncoded": false, 261 | "isSerialized": true, 262 | "isSigningField": true, 263 | "type": "UInt32" 264 | } 265 | ], 266 | [ 267 | "LowQualityOut", 268 | { 269 | "nth": 19, 270 | "isVLEncoded": false, 271 | "isSerialized": true, 272 | "isSigningField": true, 273 | "type": "UInt32" 274 | } 275 | ], 276 | [ 277 | "QualityIn", 278 | { 279 | "nth": 20, 280 | "isVLEncoded": false, 281 | "isSerialized": true, 282 | "isSigningField": true, 283 | "type": "UInt32" 284 | } 285 | ], 286 | [ 287 | "QualityOut", 288 | { 289 | "nth": 21, 290 | "isVLEncoded": false, 291 | "isSerialized": true, 292 | "isSigningField": true, 293 | "type": "UInt32" 294 | } 295 | ], 296 | [ 297 | "StampEscrow", 298 | { 299 | "nth": 22, 300 | "isVLEncoded": false, 301 | "isSerialized": true, 302 | "isSigningField": true, 303 | "type": "UInt32" 304 | } 305 | ], 306 | [ 307 | "BondAmount", 308 | { 309 | "nth": 23, 310 | "isVLEncoded": false, 311 | "isSerialized": true, 312 | "isSigningField": true, 313 | "type": "UInt32" 314 | } 315 | ], 316 | [ 317 | "LoadFee", 318 | { 319 | "nth": 24, 320 | "isVLEncoded": false, 321 | "isSerialized": true, 322 | "isSigningField": true, 323 | "type": "UInt32" 324 | } 325 | ], 326 | [ 327 | "OfferSequence", 328 | { 329 | "nth": 25, 330 | "isVLEncoded": false, 331 | "isSerialized": true, 332 | "isSigningField": true, 333 | "type": "UInt32" 334 | } 335 | ], 336 | [ 337 | "FirstLedgerSequence", 338 | { 339 | "nth": 26, 340 | "isVLEncoded": false, 341 | "isSerialized": true, 342 | "isSigningField": true, 343 | "type": "UInt32" 344 | } 345 | ], 346 | [ 347 | "LastLedgerSequence", 348 | { 349 | "nth": 27, 350 | "isVLEncoded": false, 351 | "isSerialized": true, 352 | "isSigningField": true, 353 | "type": "UInt32" 354 | } 355 | ], 356 | [ 357 | "TransactionIndex", 358 | { 359 | "nth": 28, 360 | "isVLEncoded": false, 361 | "isSerialized": true, 362 | "isSigningField": true, 363 | "type": "UInt32" 364 | } 365 | ], 366 | [ 367 | "OperationLimit", 368 | { 369 | "nth": 29, 370 | "isVLEncoded": false, 371 | "isSerialized": true, 372 | "isSigningField": true, 373 | "type": "UInt32" 374 | } 375 | ], 376 | [ 377 | "ReferenceFeeUnits", 378 | { 379 | "nth": 30, 380 | "isVLEncoded": false, 381 | "isSerialized": true, 382 | "isSigningField": true, 383 | "type": "UInt32" 384 | } 385 | ], 386 | [ 387 | "ReserveBase", 388 | { 389 | "nth": 31, 390 | "isVLEncoded": false, 391 | "isSerialized": true, 392 | "isSigningField": true, 393 | "type": "UInt32" 394 | } 395 | ], 396 | [ 397 | "ReserveIncrement", 398 | { 399 | "nth": 32, 400 | "isVLEncoded": false, 401 | "isSerialized": true, 402 | "isSigningField": true, 403 | "type": "UInt32" 404 | } 405 | ], 406 | [ 407 | "SetFlag", 408 | { 409 | "nth": 33, 410 | "isVLEncoded": false, 411 | "isSerialized": true, 412 | "isSigningField": true, 413 | "type": "UInt32" 414 | } 415 | ], 416 | [ 417 | "ClearFlag", 418 | { 419 | "nth": 34, 420 | "isVLEncoded": false, 421 | "isSerialized": true, 422 | "isSigningField": true, 423 | "type": "UInt32" 424 | } 425 | ], 426 | [ 427 | "SignerQuorum", 428 | { 429 | "nth": 35, 430 | "isVLEncoded": false, 431 | "isSerialized": true, 432 | "isSigningField": true, 433 | "type": "UInt32" 434 | } 435 | ], 436 | [ 437 | "CancelAfter", 438 | { 439 | "nth": 36, 440 | "isVLEncoded": false, 441 | "isSerialized": true, 442 | "isSigningField": true, 443 | "type": "UInt32" 444 | } 445 | ], 446 | [ 447 | "FinishAfter", 448 | { 449 | "nth": 37, 450 | "isVLEncoded": false, 451 | "isSerialized": true, 452 | "isSigningField": true, 453 | "type": "UInt32" 454 | } 455 | ], 456 | [ 457 | "IndexNext", 458 | { 459 | "nth": 1, 460 | "isVLEncoded": false, 461 | "isSerialized": true, 462 | "isSigningField": true, 463 | "type": "UInt64" 464 | } 465 | ], 466 | [ 467 | "IndexPrevious", 468 | { 469 | "nth": 2, 470 | "isVLEncoded": false, 471 | "isSerialized": true, 472 | "isSigningField": true, 473 | "type": "UInt64" 474 | } 475 | ], 476 | [ 477 | "BookNode", 478 | { 479 | "nth": 3, 480 | "isVLEncoded": false, 481 | "isSerialized": true, 482 | "isSigningField": true, 483 | "type": "UInt64" 484 | } 485 | ], 486 | [ 487 | "OwnerNode", 488 | { 489 | "nth": 4, 490 | "isVLEncoded": false, 491 | "isSerialized": true, 492 | "isSigningField": true, 493 | "type": "UInt64" 494 | } 495 | ], 496 | [ 497 | "BaseFee", 498 | { 499 | "nth": 5, 500 | "isVLEncoded": false, 501 | "isSerialized": true, 502 | "isSigningField": true, 503 | "type": "UInt64" 504 | } 505 | ], 506 | [ 507 | "ExchangeRate", 508 | { 509 | "nth": 6, 510 | "isVLEncoded": false, 511 | "isSerialized": true, 512 | "isSigningField": true, 513 | "type": "UInt64" 514 | } 515 | ], 516 | [ 517 | "LowNode", 518 | { 519 | "nth": 7, 520 | "isVLEncoded": false, 521 | "isSerialized": true, 522 | "isSigningField": true, 523 | "type": "UInt64" 524 | } 525 | ], 526 | [ 527 | "HighNode", 528 | { 529 | "nth": 8, 530 | "isVLEncoded": false, 531 | "isSerialized": true, 532 | "isSigningField": true, 533 | "type": "UInt64" 534 | } 535 | ], 536 | [ 537 | "EmailHash", 538 | { 539 | "nth": 1, 540 | "isVLEncoded": false, 541 | "isSerialized": true, 542 | "isSigningField": true, 543 | "type": "Hash128" 544 | } 545 | ], 546 | [ 547 | "LedgerHash", 548 | { 549 | "nth": 1, 550 | "isVLEncoded": false, 551 | "isSerialized": true, 552 | "isSigningField": true, 553 | "type": "Hash256" 554 | } 555 | ], 556 | [ 557 | "ParentHash", 558 | { 559 | "nth": 2, 560 | "isVLEncoded": false, 561 | "isSerialized": true, 562 | "isSigningField": true, 563 | "type": "Hash256" 564 | } 565 | ], 566 | [ 567 | "TransactionHash", 568 | { 569 | "nth": 3, 570 | "isVLEncoded": false, 571 | "isSerialized": true, 572 | "isSigningField": true, 573 | "type": "Hash256" 574 | } 575 | ], 576 | [ 577 | "AccountHash", 578 | { 579 | "nth": 4, 580 | "isVLEncoded": false, 581 | "isSerialized": true, 582 | "isSigningField": true, 583 | "type": "Hash256" 584 | } 585 | ], 586 | [ 587 | "PreviousTxnID", 588 | { 589 | "nth": 5, 590 | "isVLEncoded": false, 591 | "isSerialized": true, 592 | "isSigningField": true, 593 | "type": "Hash256" 594 | } 595 | ], 596 | [ 597 | "LedgerIndex", 598 | { 599 | "nth": 6, 600 | "isVLEncoded": false, 601 | "isSerialized": true, 602 | "isSigningField": true, 603 | "type": "Hash256" 604 | } 605 | ], 606 | [ 607 | "WalletLocator", 608 | { 609 | "nth": 7, 610 | "isVLEncoded": false, 611 | "isSerialized": true, 612 | "isSigningField": true, 613 | "type": "Hash256" 614 | } 615 | ], 616 | [ 617 | "RootIndex", 618 | { 619 | "nth": 8, 620 | "isVLEncoded": false, 621 | "isSerialized": true, 622 | "isSigningField": true, 623 | "type": "Hash256" 624 | } 625 | ], 626 | [ 627 | "AccountTxnID", 628 | { 629 | "nth": 9, 630 | "isVLEncoded": false, 631 | "isSerialized": true, 632 | "isSigningField": true, 633 | "type": "Hash256" 634 | } 635 | ], 636 | [ 637 | "BookDirectory", 638 | { 639 | "nth": 16, 640 | "isVLEncoded": false, 641 | "isSerialized": true, 642 | "isSigningField": true, 643 | "type": "Hash256" 644 | } 645 | ], 646 | [ 647 | "InvoiceID", 648 | { 649 | "nth": 17, 650 | "isVLEncoded": false, 651 | "isSerialized": true, 652 | "isSigningField": true, 653 | "type": "Hash256" 654 | } 655 | ], 656 | [ 657 | "Nickname", 658 | { 659 | "nth": 18, 660 | "isVLEncoded": false, 661 | "isSerialized": true, 662 | "isSigningField": true, 663 | "type": "Hash256" 664 | } 665 | ], 666 | [ 667 | "Amendment", 668 | { 669 | "nth": 19, 670 | "isVLEncoded": false, 671 | "isSerialized": true, 672 | "isSigningField": true, 673 | "type": "Hash256" 674 | } 675 | ], 676 | [ 677 | "TicketID", 678 | { 679 | "nth": 20, 680 | "isVLEncoded": false, 681 | "isSerialized": true, 682 | "isSigningField": true, 683 | "type": "Hash256" 684 | } 685 | ], 686 | [ 687 | "Digest", 688 | { 689 | "nth": 21, 690 | "isVLEncoded": false, 691 | "isSerialized": true, 692 | "isSigningField": true, 693 | "type": "Hash256" 694 | } 695 | ], 696 | [ 697 | "hash", 698 | { 699 | "nth": 257, 700 | "isVLEncoded": false, 701 | "isSerialized": false, 702 | "isSigningField": false, 703 | "type": "Hash256" 704 | } 705 | ], 706 | [ 707 | "index", 708 | { 709 | "nth": 258, 710 | "isVLEncoded": false, 711 | "isSerialized": false, 712 | "isSigningField": false, 713 | "type": "Hash256" 714 | } 715 | ], 716 | [ 717 | "Amount", 718 | { 719 | "nth": 1, 720 | "isVLEncoded": false, 721 | "isSerialized": true, 722 | "isSigningField": true, 723 | "type": "Amount" 724 | } 725 | ], 726 | [ 727 | "Balance", 728 | { 729 | "nth": 2, 730 | "isVLEncoded": false, 731 | "isSerialized": true, 732 | "isSigningField": true, 733 | "type": "Amount" 734 | } 735 | ], 736 | [ 737 | "LimitAmount", 738 | { 739 | "nth": 3, 740 | "isVLEncoded": false, 741 | "isSerialized": true, 742 | "isSigningField": true, 743 | "type": "Amount" 744 | } 745 | ], 746 | [ 747 | "TakerPays", 748 | { 749 | "nth": 4, 750 | "isVLEncoded": false, 751 | "isSerialized": true, 752 | "isSigningField": true, 753 | "type": "Amount" 754 | } 755 | ], 756 | [ 757 | "TakerGets", 758 | { 759 | "nth": 5, 760 | "isVLEncoded": false, 761 | "isSerialized": true, 762 | "isSigningField": true, 763 | "type": "Amount" 764 | } 765 | ], 766 | [ 767 | "LowLimit", 768 | { 769 | "nth": 6, 770 | "isVLEncoded": false, 771 | "isSerialized": true, 772 | "isSigningField": true, 773 | "type": "Amount" 774 | } 775 | ], 776 | [ 777 | "HighLimit", 778 | { 779 | "nth": 7, 780 | "isVLEncoded": false, 781 | "isSerialized": true, 782 | "isSigningField": true, 783 | "type": "Amount" 784 | } 785 | ], 786 | [ 787 | "Fee", 788 | { 789 | "nth": 8, 790 | "isVLEncoded": false, 791 | "isSerialized": true, 792 | "isSigningField": true, 793 | "type": "Amount" 794 | } 795 | ], 796 | [ 797 | "SendMax", 798 | { 799 | "nth": 9, 800 | "isVLEncoded": false, 801 | "isSerialized": true, 802 | "isSigningField": true, 803 | "type": "Amount" 804 | } 805 | ], 806 | [ 807 | "DeliverMin", 808 | { 809 | "nth": 10, 810 | "isVLEncoded": false, 811 | "isSerialized": true, 812 | "isSigningField": true, 813 | "type": "Amount" 814 | } 815 | ], 816 | [ 817 | "MinimumOffer", 818 | { 819 | "nth": 16, 820 | "isVLEncoded": false, 821 | "isSerialized": true, 822 | "isSigningField": true, 823 | "type": "Amount" 824 | } 825 | ], 826 | [ 827 | "RippleEscrow", 828 | { 829 | "nth": 17, 830 | "isVLEncoded": false, 831 | "isSerialized": true, 832 | "isSigningField": true, 833 | "type": "Amount" 834 | } 835 | ], 836 | [ 837 | "DeliveredAmount", 838 | { 839 | "nth": 18, 840 | "isVLEncoded": false, 841 | "isSerialized": true, 842 | "isSigningField": true, 843 | "type": "Amount" 844 | } 845 | ], 846 | [ 847 | "taker_gets_funded", 848 | { 849 | "nth": 258, 850 | "isVLEncoded": false, 851 | "isSerialized": false, 852 | "isSigningField": false, 853 | "type": "Amount" 854 | } 855 | ], 856 | [ 857 | "taker_pays_funded", 858 | { 859 | "nth": 259, 860 | "isVLEncoded": false, 861 | "isSerialized": false, 862 | "isSigningField": false, 863 | "type": "Amount" 864 | } 865 | ], 866 | [ 867 | "PublicKey", 868 | { 869 | "nth": 1, 870 | "isVLEncoded": true, 871 | "isSerialized": true, 872 | "isSigningField": true, 873 | "type": "Blob" 874 | } 875 | ], 876 | [ 877 | "MessageKey", 878 | { 879 | "nth": 2, 880 | "isVLEncoded": true, 881 | "isSerialized": true, 882 | "isSigningField": true, 883 | "type": "Blob" 884 | } 885 | ], 886 | [ 887 | "SigningPubKey", 888 | { 889 | "nth": 3, 890 | "isVLEncoded": true, 891 | "isSerialized": true, 892 | "isSigningField": true, 893 | "type": "Blob" 894 | } 895 | ], 896 | [ 897 | "TxnSignature", 898 | { 899 | "nth": 4, 900 | "isVLEncoded": true, 901 | "isSerialized": true, 902 | "isSigningField": false, 903 | "type": "Blob" 904 | } 905 | ], 906 | [ 907 | "Generator", 908 | { 909 | "nth": 5, 910 | "isVLEncoded": true, 911 | "isSerialized": true, 912 | "isSigningField": true, 913 | "type": "Blob" 914 | } 915 | ], 916 | [ 917 | "Signature", 918 | { 919 | "nth": 6, 920 | "isVLEncoded": true, 921 | "isSerialized": true, 922 | "isSigningField": false, 923 | "type": "Blob" 924 | } 925 | ], 926 | [ 927 | "Domain", 928 | { 929 | "nth": 7, 930 | "isVLEncoded": true, 931 | "isSerialized": true, 932 | "isSigningField": true, 933 | "type": "Blob" 934 | } 935 | ], 936 | [ 937 | "FundCode", 938 | { 939 | "nth": 8, 940 | "isVLEncoded": true, 941 | "isSerialized": true, 942 | "isSigningField": true, 943 | "type": "Blob" 944 | } 945 | ], 946 | [ 947 | "RemoveCode", 948 | { 949 | "nth": 9, 950 | "isVLEncoded": true, 951 | "isSerialized": true, 952 | "isSigningField": true, 953 | "type": "Blob" 954 | } 955 | ], 956 | [ 957 | "ExpireCode", 958 | { 959 | "nth": 10, 960 | "isVLEncoded": true, 961 | "isSerialized": true, 962 | "isSigningField": true, 963 | "type": "Blob" 964 | } 965 | ], 966 | [ 967 | "CreateCode", 968 | { 969 | "nth": 11, 970 | "isVLEncoded": true, 971 | "isSerialized": true, 972 | "isSigningField": true, 973 | "type": "Blob" 974 | } 975 | ], 976 | [ 977 | "MemoType", 978 | { 979 | "nth": 12, 980 | "isVLEncoded": true, 981 | "isSerialized": true, 982 | "isSigningField": true, 983 | "type": "Blob" 984 | } 985 | ], 986 | [ 987 | "MemoData", 988 | { 989 | "nth": 13, 990 | "isVLEncoded": true, 991 | "isSerialized": true, 992 | "isSigningField": true, 993 | "type": "Blob" 994 | } 995 | ], 996 | [ 997 | "MemoFormat", 998 | { 999 | "nth": 14, 1000 | "isVLEncoded": true, 1001 | "isSerialized": true, 1002 | "isSigningField": true, 1003 | "type": "Blob" 1004 | } 1005 | ], 1006 | [ 1007 | "Fulfillment", 1008 | { 1009 | "nth": 16, 1010 | "isVLEncoded": true, 1011 | "isSerialized": true, 1012 | "isSigningField": true, 1013 | "type": "Blob" 1014 | } 1015 | ], 1016 | [ 1017 | "Condition", 1018 | { 1019 | "nth": 17, 1020 | "isVLEncoded": true, 1021 | "isSerialized": true, 1022 | "isSigningField": true, 1023 | "type": "Blob" 1024 | } 1025 | ], 1026 | [ 1027 | "MasterSignature", 1028 | { 1029 | "nth": 18, 1030 | "isVLEncoded": true, 1031 | "isSerialized": true, 1032 | "isSigningField": false, 1033 | "type": "Blob" 1034 | } 1035 | ], 1036 | [ 1037 | "Account", 1038 | { 1039 | "nth": 1, 1040 | "isVLEncoded": true, 1041 | "isSerialized": true, 1042 | "isSigningField": true, 1043 | "type": "AccountID" 1044 | } 1045 | ], 1046 | [ 1047 | "Owner", 1048 | { 1049 | "nth": 2, 1050 | "isVLEncoded": true, 1051 | "isSerialized": true, 1052 | "isSigningField": true, 1053 | "type": "AccountID" 1054 | } 1055 | ], 1056 | [ 1057 | "Destination", 1058 | { 1059 | "nth": 3, 1060 | "isVLEncoded": true, 1061 | "isSerialized": true, 1062 | "isSigningField": true, 1063 | "type": "AccountID" 1064 | } 1065 | ], 1066 | [ 1067 | "Issuer", 1068 | { 1069 | "nth": 4, 1070 | "isVLEncoded": true, 1071 | "isSerialized": true, 1072 | "isSigningField": true, 1073 | "type": "AccountID" 1074 | } 1075 | ], 1076 | [ 1077 | "Authorize", 1078 | { 1079 | "nth": 5, 1080 | "isVLEncoded": true, 1081 | "isSerialized": true, 1082 | "isSigningField": true, 1083 | "type": "AccountID" 1084 | } 1085 | ], 1086 | [ 1087 | "Unauthorize", 1088 | { 1089 | "nth": 6, 1090 | "isVLEncoded": true, 1091 | "isSerialized": true, 1092 | "isSigningField": true, 1093 | "type": "AccountID" 1094 | } 1095 | ], 1096 | [ 1097 | "Target", 1098 | { 1099 | "nth": 7, 1100 | "isVLEncoded": true, 1101 | "isSerialized": true, 1102 | "isSigningField": true, 1103 | "type": "AccountID" 1104 | } 1105 | ], 1106 | [ 1107 | "RegularKey", 1108 | { 1109 | "nth": 8, 1110 | "isVLEncoded": true, 1111 | "isSerialized": true, 1112 | "isSigningField": true, 1113 | "type": "AccountID" 1114 | } 1115 | ], 1116 | [ 1117 | "ObjectEndMarker", 1118 | { 1119 | "nth": 1, 1120 | "isVLEncoded": false, 1121 | "isSerialized": true, 1122 | "isSigningField": true, 1123 | "type": "STObject" 1124 | } 1125 | ], 1126 | [ 1127 | "TransactionMetaData", 1128 | { 1129 | "nth": 2, 1130 | "isVLEncoded": false, 1131 | "isSerialized": true, 1132 | "isSigningField": true, 1133 | "type": "STObject" 1134 | } 1135 | ], 1136 | [ 1137 | "CreatedNode", 1138 | { 1139 | "nth": 3, 1140 | "isVLEncoded": false, 1141 | "isSerialized": true, 1142 | "isSigningField": true, 1143 | "type": "STObject" 1144 | } 1145 | ], 1146 | [ 1147 | "DeletedNode", 1148 | { 1149 | "nth": 4, 1150 | "isVLEncoded": false, 1151 | "isSerialized": true, 1152 | "isSigningField": true, 1153 | "type": "STObject" 1154 | } 1155 | ], 1156 | [ 1157 | "ModifiedNode", 1158 | { 1159 | "nth": 5, 1160 | "isVLEncoded": false, 1161 | "isSerialized": true, 1162 | "isSigningField": true, 1163 | "type": "STObject" 1164 | } 1165 | ], 1166 | [ 1167 | "PreviousFields", 1168 | { 1169 | "nth": 6, 1170 | "isVLEncoded": false, 1171 | "isSerialized": true, 1172 | "isSigningField": true, 1173 | "type": "STObject" 1174 | } 1175 | ], 1176 | [ 1177 | "FinalFields", 1178 | { 1179 | "nth": 7, 1180 | "isVLEncoded": false, 1181 | "isSerialized": true, 1182 | "isSigningField": true, 1183 | "type": "STObject" 1184 | } 1185 | ], 1186 | [ 1187 | "NewFields", 1188 | { 1189 | "nth": 8, 1190 | "isVLEncoded": false, 1191 | "isSerialized": true, 1192 | "isSigningField": true, 1193 | "type": "STObject" 1194 | } 1195 | ], 1196 | [ 1197 | "TemplateEntry", 1198 | { 1199 | "nth": 9, 1200 | "isVLEncoded": false, 1201 | "isSerialized": true, 1202 | "isSigningField": true, 1203 | "type": "STObject" 1204 | } 1205 | ], 1206 | [ 1207 | "Memo", 1208 | { 1209 | "nth": 10, 1210 | "isVLEncoded": false, 1211 | "isSerialized": true, 1212 | "isSigningField": true, 1213 | "type": "STObject" 1214 | } 1215 | ], 1216 | [ 1217 | "SignerEntry", 1218 | { 1219 | "nth": 11, 1220 | "isVLEncoded": false, 1221 | "isSerialized": true, 1222 | "isSigningField": true, 1223 | "type": "STObject" 1224 | } 1225 | ], 1226 | [ 1227 | "Signer", 1228 | { 1229 | "nth": 16, 1230 | "isVLEncoded": false, 1231 | "isSerialized": true, 1232 | "isSigningField": true, 1233 | "type": "STObject" 1234 | } 1235 | ], 1236 | [ 1237 | "Majority", 1238 | { 1239 | "nth": 18, 1240 | "isVLEncoded": false, 1241 | "isSerialized": true, 1242 | "isSigningField": true, 1243 | "type": "STObject" 1244 | } 1245 | ], 1246 | [ 1247 | "ArrayEndMarker", 1248 | { 1249 | "nth": 1, 1250 | "isVLEncoded": false, 1251 | "isSerialized": true, 1252 | "isSigningField": true, 1253 | "type": "STArray" 1254 | } 1255 | ], 1256 | [ 1257 | "Signers", 1258 | { 1259 | "nth": 3, 1260 | "isVLEncoded": false, 1261 | "isSerialized": true, 1262 | "isSigningField": false, 1263 | "type": "STArray" 1264 | } 1265 | ], 1266 | [ 1267 | "SignerEntries", 1268 | { 1269 | "nth": 4, 1270 | "isVLEncoded": false, 1271 | "isSerialized": true, 1272 | "isSigningField": true, 1273 | "type": "STArray" 1274 | } 1275 | ], 1276 | [ 1277 | "Template", 1278 | { 1279 | "nth": 5, 1280 | "isVLEncoded": false, 1281 | "isSerialized": true, 1282 | "isSigningField": true, 1283 | "type": "STArray" 1284 | } 1285 | ], 1286 | [ 1287 | "Necessary", 1288 | { 1289 | "nth": 6, 1290 | "isVLEncoded": false, 1291 | "isSerialized": true, 1292 | "isSigningField": true, 1293 | "type": "STArray" 1294 | } 1295 | ], 1296 | [ 1297 | "Sufficient", 1298 | { 1299 | "nth": 7, 1300 | "isVLEncoded": false, 1301 | "isSerialized": true, 1302 | "isSigningField": true, 1303 | "type": "STArray" 1304 | } 1305 | ], 1306 | [ 1307 | "AffectedNodes", 1308 | { 1309 | "nth": 8, 1310 | "isVLEncoded": false, 1311 | "isSerialized": true, 1312 | "isSigningField": true, 1313 | "type": "STArray" 1314 | } 1315 | ], 1316 | [ 1317 | "Memos", 1318 | { 1319 | "nth": 9, 1320 | "isVLEncoded": false, 1321 | "isSerialized": true, 1322 | "isSigningField": true, 1323 | "type": "STArray" 1324 | } 1325 | ], 1326 | [ 1327 | "Majorities", 1328 | { 1329 | "nth": 16, 1330 | "isVLEncoded": false, 1331 | "isSerialized": true, 1332 | "isSigningField": true, 1333 | "type": "STArray" 1334 | } 1335 | ], 1336 | [ 1337 | "CloseResolution", 1338 | { 1339 | "nth": 1, 1340 | "isVLEncoded": false, 1341 | "isSerialized": true, 1342 | "isSigningField": true, 1343 | "type": "UInt8" 1344 | } 1345 | ], 1346 | [ 1347 | "Method", 1348 | { 1349 | "nth": 2, 1350 | "isVLEncoded": false, 1351 | "isSerialized": true, 1352 | "isSigningField": true, 1353 | "type": "UInt8" 1354 | } 1355 | ], 1356 | [ 1357 | "TransactionResult", 1358 | { 1359 | "nth": 3, 1360 | "isVLEncoded": false, 1361 | "isSerialized": true, 1362 | "isSigningField": true, 1363 | "type": "UInt8" 1364 | } 1365 | ], 1366 | [ 1367 | "TakerPaysCurrency", 1368 | { 1369 | "nth": 1, 1370 | "isVLEncoded": false, 1371 | "isSerialized": true, 1372 | "isSigningField": true, 1373 | "type": "Hash160" 1374 | } 1375 | ], 1376 | [ 1377 | "TakerPaysIssuer", 1378 | { 1379 | "nth": 2, 1380 | "isVLEncoded": false, 1381 | "isSerialized": true, 1382 | "isSigningField": true, 1383 | "type": "Hash160" 1384 | } 1385 | ], 1386 | [ 1387 | "TakerGetsCurrency", 1388 | { 1389 | "nth": 3, 1390 | "isVLEncoded": false, 1391 | "isSerialized": true, 1392 | "isSigningField": true, 1393 | "type": "Hash160" 1394 | } 1395 | ], 1396 | [ 1397 | "TakerGetsIssuer", 1398 | { 1399 | "nth": 4, 1400 | "isVLEncoded": false, 1401 | "isSerialized": true, 1402 | "isSigningField": true, 1403 | "type": "Hash160" 1404 | } 1405 | ], 1406 | [ 1407 | "Paths", 1408 | { 1409 | "nth": 1, 1410 | "isVLEncoded": false, 1411 | "isSerialized": true, 1412 | "isSigningField": true, 1413 | "type": "PathSet" 1414 | } 1415 | ], 1416 | [ 1417 | "Indexes", 1418 | { 1419 | "nth": 1, 1420 | "isVLEncoded": true, 1421 | "isSerialized": true, 1422 | "isSigningField": true, 1423 | "type": "Vector256" 1424 | } 1425 | ], 1426 | [ 1427 | "Hashes", 1428 | { 1429 | "nth": 2, 1430 | "isVLEncoded": true, 1431 | "isSerialized": true, 1432 | "isSigningField": true, 1433 | "type": "Vector256" 1434 | } 1435 | ], 1436 | [ 1437 | "Amendments", 1438 | { 1439 | "nth": 3, 1440 | "isVLEncoded": true, 1441 | "isSerialized": true, 1442 | "isSigningField": true, 1443 | "type": "Vector256" 1444 | } 1445 | ], 1446 | [ 1447 | "Transaction", 1448 | { 1449 | "nth": 1, 1450 | "isVLEncoded": false, 1451 | "isSerialized": false, 1452 | "isSigningField": false, 1453 | "type": "Transaction" 1454 | } 1455 | ], 1456 | [ 1457 | "LedgerEntry", 1458 | { 1459 | "nth": 1, 1460 | "isVLEncoded": false, 1461 | "isSerialized": false, 1462 | "isSigningField": false, 1463 | "type": "LedgerEntry" 1464 | } 1465 | ], 1466 | [ 1467 | "Validation", 1468 | { 1469 | "nth": 1, 1470 | "isVLEncoded": false, 1471 | "isSerialized": false, 1472 | "isSigningField": false, 1473 | "type": "Validation" 1474 | } 1475 | ], 1476 | [ 1477 | "SignerListID", 1478 | { 1479 | "nth": 38, 1480 | "isVLEncoded": false, 1481 | "isSerialized": true, 1482 | "isSigningField": true, 1483 | "type": "UInt32" 1484 | } 1485 | ], 1486 | [ 1487 | "SettleDelay", 1488 | { 1489 | "nth": 39, 1490 | "isVLEncoded": false, 1491 | "isSerialized": true, 1492 | "isSigningField": true, 1493 | "type": "UInt32" 1494 | } 1495 | ], 1496 | [ 1497 | "Channel", 1498 | { 1499 | "nth": 22, 1500 | "isVLEncoded": false, 1501 | "isSerialized": true, 1502 | "isSigningField": true, 1503 | "type": "Hash256" 1504 | } 1505 | ], 1506 | [ 1507 | "ConsensusHash", 1508 | { 1509 | "nth": 23, 1510 | "isVLEncoded": false, 1511 | "isSerialized": true, 1512 | "isSigningField": true, 1513 | "type": "Hash256" 1514 | } 1515 | ], 1516 | [ 1517 | "CheckID", 1518 | { 1519 | "nth": 24, 1520 | "isVLEncoded": false, 1521 | "isSerialized": true, 1522 | "isSigningField": true, 1523 | "type": "Hash256" 1524 | } 1525 | ], 1526 | [ 1527 | "TickSize", 1528 | { 1529 | "nth": 16, 1530 | "isVLEncoded": false, 1531 | "isSerialized": true, 1532 | "isSigningField": true, 1533 | "type": "UInt8" 1534 | } 1535 | ], 1536 | [ 1537 | "DestinationNode", 1538 | { 1539 | "nth": 9, 1540 | "isVLEncoded": false, 1541 | "isSerialized": true, 1542 | "isSigningField": true, 1543 | "type": "UInt64" 1544 | } 1545 | ] 1546 | ], 1547 | "TRANSACTION_RESULTS": { 1548 | "telNO_DST_PARTIAL": -393, 1549 | "temBAD_SRC_ACCOUNT": -281, 1550 | "tefPAST_SEQ": -189, 1551 | "terNO_ACCOUNT": -96, 1552 | "temREDUNDANT": -275, 1553 | "tefCREATED": -194, 1554 | "temDST_IS_SRC": -279, 1555 | "terRETRY": -99, 1556 | "temINVALID_FLAG": -276, 1557 | "temBAD_SEND_XRP_LIMIT": -288, 1558 | "terNO_LINE": -94, 1559 | "tefBAD_AUTH": -196, 1560 | "temBAD_EXPIRATION": -295, 1561 | "temBAD_SEND_XRP_NO_DIRECT": -286, 1562 | "temBAD_SEND_XRP_PATHS": -284, 1563 | "tefBAD_LEDGER": -195, 1564 | "tefNO_AUTH_REQUIRED": -190, 1565 | "terOWNERS": -93, 1566 | "terLAST": -91, 1567 | "terNO_RIPPLE": -90, 1568 | "temBAD_FEE": -294, 1569 | "terPRE_SEQ": -92, 1570 | "tefMASTER_DISABLED": -187, 1571 | "temBAD_CURRENCY": -296, 1572 | "tefDST_TAG_NEEDED": -193, 1573 | "temBAD_SIGNATURE": -282, 1574 | "tefFAILURE": -199, 1575 | "telBAD_PATH_COUNT": -397, 1576 | "temBAD_TRANSFER_RATE": -280, 1577 | "tefWRONG_PRIOR": -188, 1578 | "telBAD_DOMAIN": -398, 1579 | "temBAD_AMOUNT": -298, 1580 | "temBAD_AUTH_MASTER": -297, 1581 | "temBAD_LIMIT": -292, 1582 | "temBAD_ISSUER": -293, 1583 | "telBAD_PUBLIC_KEY": -396, 1584 | "tefBAD_ADD_AUTH": -197, 1585 | "temBAD_OFFER": -291, 1586 | "temBAD_SEND_XRP_PARTIAL": -285, 1587 | "temDST_NEEDED": -278, 1588 | "tefALREADY": -198, 1589 | "temUNCERTAIN": -272, 1590 | "telLOCAL_ERROR": -399, 1591 | "temREDUNDANT_SEND_MAX": -274, 1592 | "tefINTERNAL": -191, 1593 | "temBAD_PATH_LOOP": -289, 1594 | "tefEXCEPTION": -192, 1595 | "temRIPPLE_EMPTY": -273, 1596 | "telINSUF_FEE_P": -394, 1597 | "temBAD_SEQUENCE": -283, 1598 | "tefMAX_LEDGER": -186, 1599 | "terFUNDS_SPENT": -98, 1600 | "temBAD_SEND_XRP_MAX": -287, 1601 | "telFAILED_PROCESSING": -395, 1602 | "terINSUF_FEE_B": -97, 1603 | "tesSUCCESS": 0, 1604 | "temBAD_PATH": -290, 1605 | "temMALFORMED": -299, 1606 | "temUNKNOWN": -271, 1607 | "temINVALID": -277, 1608 | "terNO_AUTH": -95, 1609 | "temBAD_TICK_SIZE": -270, 1610 | 1611 | "tecCLAIM": 100, 1612 | "tecPATH_PARTIAL": 101, 1613 | "tecUNFUNDED_ADD": 102, 1614 | "tecUNFUNDED_OFFER": 103, 1615 | "tecUNFUNDED_PAYMENT": 104, 1616 | "tecFAILED_PROCESSING": 105, 1617 | "tecDIR_FULL": 121, 1618 | "tecINSUF_RESERVE_LINE": 122, 1619 | "tecINSUF_RESERVE_OFFER": 123, 1620 | "tecNO_DST": 124, 1621 | "tecNO_DST_INSUF_XRP": 125, 1622 | "tecNO_LINE_INSUF_RESERVE": 126, 1623 | "tecNO_LINE_REDUNDANT": 127, 1624 | "tecPATH_DRY": 128, 1625 | "tecUNFUNDED": 129, 1626 | "tecNO_ALTERNATIVE_KEY": 130, 1627 | "tecNO_REGULAR_KEY": 131, 1628 | "tecOWNERS": 132, 1629 | "tecNO_ISSUER": 133, 1630 | "tecNO_AUTH": 134, 1631 | "tecNO_LINE": 135, 1632 | "tecINSUFF_FEE": 136, 1633 | "tecFROZEN": 137, 1634 | "tecNO_TARGET": 138, 1635 | "tecNO_PERMISSION": 139, 1636 | "tecNO_ENTRY": 140, 1637 | "tecINSUFFICIENT_RESERVE": 141, 1638 | "tecNEED_MASTER_KEY": 142, 1639 | "tecDST_TAG_NEEDED": 143, 1640 | "tecINTERNAL": 144, 1641 | "tecOVERSIZE": 145, 1642 | "tecCRYPTOCONDITION_ERROR": 146, 1643 | "tecINVARIANT_FAILED": 147, 1644 | "tecEXPIRED": 148, 1645 | "tecDUPLICATE": 149 1646 | }, 1647 | "TRANSACTION_TYPES": { 1648 | "Invalid": -1, 1649 | "Payment": 0, 1650 | "EscrowCreate": 1, 1651 | "EscrowFinish": 2, 1652 | "AccountSet": 3, 1653 | "EscrowCancel": 4, 1654 | "SetRegularKey": 5, 1655 | "NickNameSet": 6, 1656 | "OfferCreate": 7, 1657 | "OfferCancel": 8, 1658 | "Contract": 9, 1659 | "TicketCreate": 10, 1660 | "TicketCancel": 11, 1661 | "SignerListSet": 12, 1662 | "PaymentChannelCreate": 13, 1663 | "PaymentChannelFund": 14, 1664 | "PaymentChannelClaim": 15, 1665 | "CheckCreate": 16, 1666 | "CheckCash": 17, 1667 | "CheckCancel": 18, 1668 | "DepositPreauth": 19, 1669 | "TrustSet": 20, 1670 | "EnableAmendment": 100, 1671 | "SetFee": 101 1672 | } 1673 | } 1674 | """ 1675 | --------------------------------------------------------------------------------