Files
koins/apps/ios/Koins/Wallet/XRPBinaryCodec.swift

136 lines
4.4 KiB
Swift

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
}
}