import Foundation /// Canonical XRP Ledger binary serialization for the Payment transactions this /// wallet sends, plus the signing / transaction-id hashing around it. struct XRPPayment { var account: String var destination: String var amountDrops: UInt64 var feeDrops: UInt64 var sequence: UInt32 var lastLedgerSequence: UInt32? var destinationTag: UInt32? var signingPublicKey: [UInt8] var signature: [UInt8]? private static let typeUInt16: UInt8 = 1 private static let typeUInt32: UInt8 = 2 private static let typeAmount: UInt8 = 6 private static let typeBlob: UInt8 = 7 private static let typeAccountID: UInt8 = 8 func serialized(includingSignature: Bool) throws -> [UInt8] { var output: [UInt8] = [] // Fields must appear in (type, field) order. output += header(type: Self.typeUInt16, field: 2) // TransactionType output += uint16(0) // ttPAYMENT output += header(type: Self.typeUInt32, field: 2) // Flags output += uint32(0) output += header(type: Self.typeUInt32, field: 4) // Sequence output += uint32(sequence) if let tag = destinationTag { output += header(type: Self.typeUInt32, field: 14) // DestinationTag output += uint32(tag) } if let lastLedger = lastLedgerSequence { output += header(type: Self.typeUInt32, field: 27) // LastLedgerSequence output += uint32(lastLedger) } output += header(type: Self.typeAmount, field: 1) // Amount output += try amount(amountDrops) output += header(type: Self.typeAmount, field: 8) // Fee output += try amount(feeDrops) output += header(type: Self.typeBlob, field: 3) // SigningPubKey output += variableLength(signingPublicKey) if includingSignature, let signature { output += header(type: Self.typeBlob, field: 4) // TxnSignature output += variableLength(signature) } output += header(type: Self.typeAccountID, field: 1) // Account output += variableLength(try XRPKey.accountId(fromAddress: account)) output += header(type: Self.typeAccountID, field: 3) // Destination output += variableLength(try XRPKey.accountId(fromAddress: destination)) return output } /// Bytes the private key signs (HashPrefix.transactionSig + tx). func signingPayload() throws -> [UInt8] { [0x53, 0x54, 0x58, 0x00] + (try serialized(includingSignature: false)) } /// The signed transaction blob submitted to the network (no hash prefix). func txBlob() throws -> [UInt8] { guard signature != nil else { throw XRPError.rpc("Transaction is not signed") } return try serialized(includingSignature: true) } func transactionHash() throws -> [UInt8] { guard signature != nil else { throw XRPError.rpc("Transaction is not signed") } let blob = try serialized(includingSignature: true) return XRPHash.sha512Half([0x54, 0x58, 0x4e, 0x00] + blob) } // MARK: - Encoding helpers private func header(type: UInt8, field: UInt8) -> [UInt8] { if field < 16 { return [(type << 4) | field] } return [type << 4, field] } private func uint16(_ value: UInt16) -> [UInt8] { [UInt8(value >> 8), UInt8(value & 0xff)] } private func uint32(_ value: UInt32) -> [UInt8] { [ UInt8((value >> 24) & 0xff), UInt8((value >> 16) & 0xff), UInt8((value >> 8) & 0xff), UInt8(value & 0xff), ] } /// Native (XRP) amount: 8 bytes big-endian drops with the XRP flag bit set. private func amount(_ drops: UInt64) throws -> [UInt8] { guard drops <= XRPAmount.maxDrops else { throw XRPError.rpc("Amount is too large") } var bytes = uint64(drops) bytes[0] |= 0x40 return bytes } private func uint64(_ value: UInt64) -> [UInt8] { var bytes: [UInt8] = [] for shift in stride(from: 56, through: 0, by: -8) { bytes.append(UInt8((value >> UInt64(shift)) & 0xff)) } return bytes } private func variableLength(_ bytes: [UInt8]) -> [UInt8] { var output: [UInt8] = [] let count = bytes.count if count < 192 { output.append(UInt8(count)) } else if count <= 12_480 { output.append(UInt8((count >> 8) + 192)) output.append(UInt8(count & 0xff)) } else { output.append(UInt8((count >> 16) + 240)) output.append(UInt8((count >> 8) & 0xff)) output.append(UInt8(count & 0xff)) } return output + bytes } }