Replace the JavaScript mobile app with a native Swift iOS wallet
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user