@authon/react-native-wallet 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +34 -4
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +57 -3
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AndroidCapabilityProbe.kt +86 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +29 -2
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bitcoin.kt +296 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Chains.kt +157 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Der.kt +17 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Encodings.kt +133 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +6 -2
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +27 -23
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Solana.kt +195 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Tron.kt +184 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +16 -8
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletCapabilities.kt +91 -0
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +18 -2
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +103 -1
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +8 -1
- package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Xrp.kt +193 -0
- package/build/client.d.ts +18 -2
- package/build/client.d.ts.map +1 -1
- package/build/client.js +88 -6
- package/build/client.js.map +1 -1
- package/build/errors.d.ts +8 -1
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +7 -1
- package/build/errors.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/native.d.ts +5 -1
- package/build/native.d.ts.map +1 -1
- package/build/native.js.map +1 -1
- package/build/types.d.ts +47 -1
- package/build/types.d.ts.map +1 -1
- package/ios/AuthonReactNativeWallet.podspec +6 -2
- package/ios/AuthonWalletModule.swift +46 -3
- package/ios/vendor/AuthonWallet/AuthonWallet.swift +24 -4
- package/ios/vendor/AuthonWallet/Capabilities.swift +149 -0
- package/ios/vendor/AuthonWallet/Chains/Bitcoin.swift +296 -0
- package/ios/vendor/AuthonWallet/Chains/Solana.swift +207 -0
- package/ios/vendor/AuthonWallet/Chains/Tron.swift +181 -0
- package/ios/vendor/AuthonWallet/Chains/XRP.swift +191 -0
- package/ios/vendor/AuthonWallet/Crypto/Chains.swift +278 -0
- package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +24 -0
- package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +19 -8
- package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +101 -2
- package/ios/vendor/AuthonWallet/WalletError.swift +32 -2
- package/ios/vendor/CKeccak/README.md +6 -3
- package/ios/vendor/CKeccak/include/ckeccak.h +4 -0
- package/ios/vendor/CKeccak/ripemd160.c +343 -0
- package/ios/vendor/CKeccak/ripemd160.h +22 -0
- package/ios/vendor/CMonocypher/LICENCE.md +173 -0
- package/ios/vendor/CMonocypher/README.md +27 -0
- package/ios/vendor/CMonocypher/include/monocypher-ed25519.h +140 -0
- package/ios/vendor/CMonocypher/include/monocypher.h +321 -0
- package/ios/vendor/CMonocypher/monocypher-ed25519.c +500 -0
- package/ios/vendor/CMonocypher/monocypher.c +2956 -0
- package/ios/vendor/GENERATED.md +1 -1
- package/package.json +3 -2
- package/src/client.ts +90 -6
- package/src/errors.ts +7 -1
- package/src/index.ts +5 -0
- package/src/native.ts +4 -2
- package/src/types.ts +52 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Chains/Bitcoin.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Bitcoin PSBT signing (docs/wallet/NATIVE.md section 12, `apps/wallet/src/bitcoin.ts`). Inputs
|
|
5
|
+
/// whose previous output is the signing account's P2WPKH script are signed (BIP143,
|
|
6
|
+
/// SIGHASH_ALL); other inputs are left alone. Every input must carry witness_utxo.
|
|
7
|
+
enum Bitcoin {
|
|
8
|
+
static let maxPsbt = 100_000
|
|
9
|
+
|
|
10
|
+
private static func invalid(_ why: String = "not a valid PSBT") -> WalletError { WalletError(.invalidRequest, why) }
|
|
11
|
+
|
|
12
|
+
struct KeyValue {
|
|
13
|
+
var key: [UInt8]
|
|
14
|
+
var value: [UInt8]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
struct Input {
|
|
18
|
+
var prevout: [UInt8] // txid (32, internal order) + vout (4)
|
|
19
|
+
var sequence: [UInt8]
|
|
20
|
+
var map: [KeyValue]
|
|
21
|
+
|
|
22
|
+
var witnessUtxo: (amount: UInt64, script: [UInt8])? {
|
|
23
|
+
guard let v = map.first(where: { $0.key == [0x01] })?.value else { return nil }
|
|
24
|
+
return try? Bitcoin.decodeTxOut(v)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
struct Output {
|
|
29
|
+
var amount: UInt64
|
|
30
|
+
var script: [UInt8]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// A version 0 PSBT. Key-value pairs are kept as read and written back in the same order.
|
|
34
|
+
struct Psbt {
|
|
35
|
+
var global: [KeyValue]
|
|
36
|
+
var version: [UInt8]
|
|
37
|
+
var locktime: [UInt8]
|
|
38
|
+
var inputs: [Input]
|
|
39
|
+
var outputs: [Output]
|
|
40
|
+
var outputMaps: [[KeyValue]]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// MARK: - Reading
|
|
44
|
+
|
|
45
|
+
private struct Reader {
|
|
46
|
+
let b: [UInt8]
|
|
47
|
+
var pos = 0
|
|
48
|
+
var done: Bool { pos == b.count }
|
|
49
|
+
|
|
50
|
+
mutating func take(_ n: Int) throws -> [UInt8] {
|
|
51
|
+
guard n >= 0, n <= b.count - pos else { throw invalid() }
|
|
52
|
+
pos += n
|
|
53
|
+
return Array(b[(pos - n)..<pos])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
mutating func compact() throws -> Int {
|
|
57
|
+
let first = try take(1)[0]
|
|
58
|
+
switch first {
|
|
59
|
+
case 0xfd: return Int(le(try take(2)))
|
|
60
|
+
case 0xfe: return Int(le(try take(4)))
|
|
61
|
+
case 0xff:
|
|
62
|
+
let v = le(try take(8))
|
|
63
|
+
guard v <= UInt64(Int32.max) else { throw invalid() }
|
|
64
|
+
return Int(v)
|
|
65
|
+
default: return Int(first)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
mutating func varBytes() throws -> [UInt8] { try take(try compact()) }
|
|
70
|
+
|
|
71
|
+
/// One map; ends at the 0x00 separator. Duplicate keys are invalid.
|
|
72
|
+
mutating func map() throws -> [KeyValue] {
|
|
73
|
+
var out = [KeyValue]()
|
|
74
|
+
while true {
|
|
75
|
+
let key = try varBytes()
|
|
76
|
+
if key.isEmpty { return out }
|
|
77
|
+
let value = try varBytes()
|
|
78
|
+
guard !out.contains(where: { $0.key == key }) else { throw invalid() }
|
|
79
|
+
out.append(KeyValue(key: key, value: value))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static func le(_ b: [UInt8]) -> UInt64 { b.enumerated().reduce(UInt64(0)) { $0 | UInt64($1.element) << (8 * UInt64($1.offset)) } }
|
|
85
|
+
static func le64(_ v: UInt64) -> [UInt8] { (0..<8).map { UInt8(truncatingIfNeeded: v >> (8 * UInt64($0))) } }
|
|
86
|
+
|
|
87
|
+
static func compact(_ n: Int) -> [UInt8] {
|
|
88
|
+
if n < 0xfd { return [UInt8(n)] }
|
|
89
|
+
if n <= 0xffff { return [0xfd, UInt8(n & 0xff), UInt8(n >> 8)] }
|
|
90
|
+
return [0xfe] + (0..<4).map { UInt8(truncatingIfNeeded: n >> (8 * $0)) }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static func decodeTxOut(_ b: [UInt8]) throws -> (amount: UInt64, script: [UInt8]) {
|
|
94
|
+
var r = Reader(b: b)
|
|
95
|
+
let amount = le(try r.take(8))
|
|
96
|
+
let script = try r.varBytes()
|
|
97
|
+
guard r.done else { throw invalid() }
|
|
98
|
+
return (amount, script)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static func parse(_ bytes: [UInt8]) throws -> Psbt {
|
|
102
|
+
guard !bytes.isEmpty, bytes.count <= maxPsbt else { throw invalid() }
|
|
103
|
+
var r = Reader(b: bytes)
|
|
104
|
+
guard try r.take(5) == [0x70, 0x73, 0x62, 0x74, 0xff] else { throw invalid() }
|
|
105
|
+
let global = try r.map()
|
|
106
|
+
if let v = global.first(where: { $0.key == [0xfb] })?.value, le(v) != 0 { throw invalid("only PSBT version 0 is supported") }
|
|
107
|
+
guard let unsigned = global.first(where: { $0.key == [0x00] })?.value else { throw invalid() }
|
|
108
|
+
var t = Reader(b: unsigned)
|
|
109
|
+
let version = try t.take(4)
|
|
110
|
+
let inCount = try t.compact()
|
|
111
|
+
guard inCount > 0 else { throw invalid() } // also rejects a segwit marker
|
|
112
|
+
var inputs = [Input]()
|
|
113
|
+
for _ in 0..<inCount {
|
|
114
|
+
let prevout = try t.take(36)
|
|
115
|
+
guard try t.varBytes().isEmpty else { throw invalid() } // unsigned: empty scriptSig
|
|
116
|
+
inputs.append(Input(prevout: prevout, sequence: try t.take(4), map: []))
|
|
117
|
+
}
|
|
118
|
+
var outputs = [Output]()
|
|
119
|
+
for _ in 0..<(try t.compact()) {
|
|
120
|
+
let amount = le(try t.take(8))
|
|
121
|
+
outputs.append(Output(amount: amount, script: try t.varBytes()))
|
|
122
|
+
}
|
|
123
|
+
let locktime = try t.take(4)
|
|
124
|
+
guard t.done else { throw invalid() }
|
|
125
|
+
for i in inputs.indices {
|
|
126
|
+
inputs[i].map = try r.map()
|
|
127
|
+
if let utxo = inputs[i].map.first(where: { $0.key == [0x01] }) { _ = try decodeTxOut(utxo.value) }
|
|
128
|
+
if let sighash = inputs[i].map.first(where: { $0.key == [0x03] })?.value, sighash != [1, 0, 0, 0] {
|
|
129
|
+
throw invalid("only SIGHASH_ALL inputs can be signed")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
var outputMaps = [[KeyValue]]()
|
|
133
|
+
for _ in outputs { outputMaps.append(try r.map()) }
|
|
134
|
+
guard r.done else { throw invalid() }
|
|
135
|
+
return Psbt(global: global, version: version, locktime: locktime, inputs: inputs, outputs: outputs, outputMaps: outputMaps)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static func serialize(_ p: Psbt) -> [UInt8] {
|
|
139
|
+
func map(_ m: [KeyValue]) -> [UInt8] {
|
|
140
|
+
var out = [UInt8]()
|
|
141
|
+
for kv in m {
|
|
142
|
+
out += compact(kv.key.count)
|
|
143
|
+
out += kv.key
|
|
144
|
+
out += compact(kv.value.count)
|
|
145
|
+
out += kv.value
|
|
146
|
+
}
|
|
147
|
+
out.append(0)
|
|
148
|
+
return out
|
|
149
|
+
}
|
|
150
|
+
var out: [UInt8] = [0x70, 0x73, 0x62, 0x74, 0xff]
|
|
151
|
+
out += map(p.global)
|
|
152
|
+
for i in p.inputs { out += map(i.map) }
|
|
153
|
+
for m in p.outputMaps { out += map(m) }
|
|
154
|
+
return out
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// MARK: - Scripts and addresses
|
|
158
|
+
|
|
159
|
+
static func p2wpkh(_ compressed: [UInt8]) -> [UInt8] { [0x00, 0x14] + ChainAddresses.hash160(compressed) }
|
|
160
|
+
|
|
161
|
+
private static func hrp(_ chain: WalletChain) -> String { chain == .bitcoin ? "bc" : "tb" }
|
|
162
|
+
|
|
163
|
+
/// Address of a standard output script, or nil.
|
|
164
|
+
static func address(script s: [UInt8], chain: WalletChain) -> String? {
|
|
165
|
+
let main = chain == .bitcoin
|
|
166
|
+
func base58(_ version: UInt8, _ hash: ArraySlice<UInt8>) -> String {
|
|
167
|
+
let payload = [version] + hash
|
|
168
|
+
return Base58.encode(payload + ChainAddresses.checksum4(payload), alphabet: Base58.bitcoin)
|
|
169
|
+
}
|
|
170
|
+
if s.count == 25, s[0] == 0x76, s[1] == 0xa9, s[2] == 0x14, s[23] == 0x88, s[24] == 0xac { return base58(main ? 0x00 : 0x6f, s[3..<23]) }
|
|
171
|
+
if s.count == 23, s[0] == 0xa9, s[1] == 0x14, s[22] == 0x87 { return base58(main ? 0x05 : 0xc4, s[2..<22]) }
|
|
172
|
+
if s.count == 22, s[0] == 0x00, s[1] == 0x14 { return Bech32.segwitAddress(hrp: hrp(chain), version: 0, program: Array(s[2...])) }
|
|
173
|
+
if s.count == 34, s[0] == 0x00, s[1] == 0x20 { return Bech32.segwitAddress(hrp: hrp(chain), version: 0, program: Array(s[2...])) }
|
|
174
|
+
if s.count == 34, s[0] == 0x51, s[1] == 0x20 { return Bech32.segwitAddress(hrp: hrp(chain), version: 1, program: Array(s[2...])) }
|
|
175
|
+
return nil
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static func btc(_ sats: UInt64) -> String { Solana.units(sats, 8) + " BTC" }
|
|
179
|
+
|
|
180
|
+
private static func ownInputs(_ p: Psbt, script: [UInt8]) -> [Int] {
|
|
181
|
+
p.inputs.indices.filter { p.inputs[$0].witnessUtxo?.script == script }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static func summarize(_ p: Psbt, signer: String, chain: WalletChain) throws -> SignSummary {
|
|
185
|
+
guard let decoded = Bech32.decodeSegwit(hrp: hrp(chain), address: signer), decoded.version == 0, decoded.program.count == 20 else {
|
|
186
|
+
throw WalletError(.invalidRequest, "address is not a Bitcoin address of this network")
|
|
187
|
+
}
|
|
188
|
+
let script = [0x00, 0x14] + decoded.program
|
|
189
|
+
var rows: [SignSummary.Row] = [
|
|
190
|
+
.init(label: "Network", value: chain == .bitcoin ? "Bitcoin" : "Bitcoin testnet"),
|
|
191
|
+
.init(label: "Account", value: signer, mono: true),
|
|
192
|
+
]
|
|
193
|
+
var warnings = [String]()
|
|
194
|
+
var inputs: UInt64 = 0
|
|
195
|
+
for i in p.inputs {
|
|
196
|
+
guard let utxo = i.witnessUtxo else { throw invalid("every input needs witness_utxo") }
|
|
197
|
+
let (sum, overflow) = inputs.addingReportingOverflow(utxo.amount)
|
|
198
|
+
guard !overflow else { throw invalid() }
|
|
199
|
+
inputs = sum
|
|
200
|
+
}
|
|
201
|
+
let mine = ownInputs(p, script: script)
|
|
202
|
+
guard !mine.isEmpty else { throw WalletError(.addressMismatch, "no input belongs to the signing account") }
|
|
203
|
+
if mine.count != p.inputs.count {
|
|
204
|
+
warnings.append("\(p.inputs.count - mine.count) of \(p.inputs.count) inputs are not yours and are not signed.")
|
|
205
|
+
}
|
|
206
|
+
rows.append(.init(label: "Spending", value: btc(inputs)))
|
|
207
|
+
var outputs: UInt64 = 0
|
|
208
|
+
for o in p.outputs {
|
|
209
|
+
let (sum, overflow) = outputs.addingReportingOverflow(o.amount)
|
|
210
|
+
guard !overflow else { throw invalid() }
|
|
211
|
+
outputs = sum
|
|
212
|
+
let to: String
|
|
213
|
+
if let a = address(script: o.script, chain: chain) {
|
|
214
|
+
to = a
|
|
215
|
+
} else {
|
|
216
|
+
to = "non-standard output"
|
|
217
|
+
warnings.append("An output has a script this wallet cannot show as an address.")
|
|
218
|
+
}
|
|
219
|
+
rows.append(o.script == script
|
|
220
|
+
? .init(label: "Change back to you", value: btc(o.amount))
|
|
221
|
+
: .init(label: "Send \(btc(o.amount)) to", value: to, mono: true))
|
|
222
|
+
}
|
|
223
|
+
guard outputs <= inputs else { throw invalid("outputs exceed inputs") }
|
|
224
|
+
let fee = inputs - outputs
|
|
225
|
+
rows.append(.init(label: "Network fee", value: btc(fee)))
|
|
226
|
+
if fee.multipliedReportingOverflow(by: 10).partialValue > inputs && fee > 100_000 {
|
|
227
|
+
warnings.append("The network fee is more than 10% of the amount spent.")
|
|
228
|
+
}
|
|
229
|
+
return SignSummary(kind: .transaction, title: "Bitcoin transaction", rows: rows, body: nil, warnings: warnings, risk: .high)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// MARK: - Signing
|
|
233
|
+
|
|
234
|
+
private static func dsha(_ b: [UInt8]) -> [UInt8] { Bytes.sha256(Bytes.sha256(b)) }
|
|
235
|
+
|
|
236
|
+
private static func serializeOutputs(_ p: Psbt) -> [UInt8] {
|
|
237
|
+
p.outputs.flatMap { le64($0.amount) + compact($0.script.count) + $0.script }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// BIP143 SIGHASH_ALL digest of input `index` spending a P2WPKH output of `pubkeyHash`.
|
|
241
|
+
static func sighash(_ p: Psbt, index: Int, pubkeyHash: [UInt8], amount: UInt64) -> [UInt8] {
|
|
242
|
+
let hashPrevouts = dsha(p.inputs.flatMap(\.prevout))
|
|
243
|
+
let hashSequence = dsha(p.inputs.flatMap(\.sequence))
|
|
244
|
+
let hashOutputs = dsha(serializeOutputs(p))
|
|
245
|
+
let scriptCode: [UInt8] = [0x19, 0x76, 0xa9, 0x14] + pubkeyHash + [0x88, 0xac]
|
|
246
|
+
let input = p.inputs[index]
|
|
247
|
+
var preimage: [UInt8] = p.version
|
|
248
|
+
preimage += hashPrevouts
|
|
249
|
+
preimage += hashSequence
|
|
250
|
+
preimage += input.prevout
|
|
251
|
+
preimage += scriptCode
|
|
252
|
+
preimage += le64(amount)
|
|
253
|
+
preimage += input.sequence
|
|
254
|
+
preimage += hashOutputs
|
|
255
|
+
preimage += p.locktime
|
|
256
|
+
preimage += [1, 0, 0, 0]
|
|
257
|
+
return dsha(preimage)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Signs the signer's inputs. Returns the signed PSBT (partial signatures added) and, when
|
|
261
|
+
/// every input was ours, the finalized raw transaction.
|
|
262
|
+
static func sign(_ psbt: Psbt, privateKey: [UInt8]) throws -> [String: String] {
|
|
263
|
+
var p = psbt
|
|
264
|
+
let pub = try Secp256k1.publicKey(of: privateKey, compressed: true)
|
|
265
|
+
let script = p2wpkh(pub)
|
|
266
|
+
let pubkeyHash = Array(script[2...])
|
|
267
|
+
let mine = ownInputs(p, script: script)
|
|
268
|
+
guard !mine.isEmpty else { throw WalletError(.addressMismatch) }
|
|
269
|
+
var witnesses = [Int: [UInt8]]()
|
|
270
|
+
for i in mine {
|
|
271
|
+
let amount = p.inputs[i].witnessUtxo!.amount
|
|
272
|
+
let sig = try Secp256k1.signDER(hash: sighash(p, index: i, pubkeyHash: pubkeyHash, amount: amount), key: privateKey) + [0x01]
|
|
273
|
+
let key = [0x02] + pub
|
|
274
|
+
if let at = p.inputs[i].map.firstIndex(where: { $0.key == key }) {
|
|
275
|
+
p.inputs[i].map[at].value = sig
|
|
276
|
+
} else {
|
|
277
|
+
p.inputs[i].map.append(KeyValue(key: key, value: sig))
|
|
278
|
+
}
|
|
279
|
+
witnesses[i] = compact(2) + compact(sig.count) + sig + compact(pub.count) + pub
|
|
280
|
+
}
|
|
281
|
+
var result = ["psbt": Bytes.base64(serialize(p))]
|
|
282
|
+
guard mine.count == p.inputs.count else { return result }
|
|
283
|
+
var tx = p.version + [0x00, 0x01] + compact(p.inputs.count)
|
|
284
|
+
for i in p.inputs { tx += i.prevout + [0x00] + i.sequence }
|
|
285
|
+
tx += compact(p.outputs.count) + serializeOutputs(p)
|
|
286
|
+
for i in p.inputs.indices { tx += witnesses[i]! }
|
|
287
|
+
tx += p.locktime
|
|
288
|
+
result["txHex"] = Bytes.hex(tx)
|
|
289
|
+
return result
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// Partial signatures (by input) for checks: the value of the PSBT_IN_PARTIAL_SIG entry for `pubkey`.
|
|
293
|
+
static func partialSignatures(_ p: Psbt, pubkey: [UInt8]) -> [[UInt8]?] {
|
|
294
|
+
p.inputs.map { i in i.map.first(where: { $0.key == [0x02] + pubkey })?.value }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Chains/Solana.swift. Do not edit: change the source and run the script.
|
|
2
|
+
#if canImport(CMonocypher)
|
|
3
|
+
import CMonocypher
|
|
4
|
+
#endif
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
/// Solana transaction signing (docs/wallet/NATIVE.md section 12, `apps/wallet/src/solana.ts`).
|
|
8
|
+
/// The app builds the serialized Message; the wallet parses it only to describe it and signs
|
|
9
|
+
/// the exact bytes.
|
|
10
|
+
enum Solana {
|
|
11
|
+
static let maxMessage = 1232
|
|
12
|
+
|
|
13
|
+
static let system = "11111111111111111111111111111111"
|
|
14
|
+
static let token = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
15
|
+
static let token2022 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
|
16
|
+
static let associatedToken = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
17
|
+
static let computeBudget = "ComputeBudget111111111111111111111111111111"
|
|
18
|
+
static let memo = "MemoSq4gqABAXKb96qnH8TyNoJ6CDrADxwaj4uuQ"
|
|
19
|
+
|
|
20
|
+
struct Instruction {
|
|
21
|
+
var programId: String
|
|
22
|
+
/// nil for an account that comes from an address lookup table.
|
|
23
|
+
var accounts: [String?]
|
|
24
|
+
var data: [UInt8]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
struct Message {
|
|
28
|
+
var isV0: Bool
|
|
29
|
+
var numRequiredSignatures: Int
|
|
30
|
+
var accountKeys: [String]
|
|
31
|
+
var recentBlockhash: String
|
|
32
|
+
var instructions: [Instruction]
|
|
33
|
+
/// Accounts loaded from address lookup tables (v0 only).
|
|
34
|
+
var lookupAccounts: Int
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private static func invalid() -> WalletError { WalletError(.invalidRequest, "not a valid Solana message") }
|
|
38
|
+
|
|
39
|
+
private struct Reader {
|
|
40
|
+
let bytes: [UInt8]
|
|
41
|
+
var pos = 0
|
|
42
|
+
var done: Bool { pos == bytes.count }
|
|
43
|
+
|
|
44
|
+
mutating func u8() throws -> Int {
|
|
45
|
+
guard pos < bytes.count else { throw invalid() }
|
|
46
|
+
pos += 1
|
|
47
|
+
return Int(bytes[pos - 1])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
mutating func take(_ n: Int) throws -> [UInt8] {
|
|
51
|
+
guard n >= 0, pos + n <= bytes.count else { throw invalid() }
|
|
52
|
+
pos += n
|
|
53
|
+
return Array(bytes[(pos - n)..<pos])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// compact-u16 (shortvec)
|
|
57
|
+
mutating func len() throws -> Int {
|
|
58
|
+
var value = 0
|
|
59
|
+
for i in 0..<3 {
|
|
60
|
+
let b = try u8()
|
|
61
|
+
value |= (b & 0x7f) << (7 * i)
|
|
62
|
+
if b & 0x80 == 0 { return value }
|
|
63
|
+
}
|
|
64
|
+
throw invalid()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static func parse(_ bytes: [UInt8]) throws -> Message {
|
|
69
|
+
guard !bytes.isEmpty, bytes.count <= maxMessage else { throw invalid() }
|
|
70
|
+
var r = Reader(bytes: bytes)
|
|
71
|
+
var isV0 = false
|
|
72
|
+
var first = try r.u8()
|
|
73
|
+
if first & 0x80 != 0 {
|
|
74
|
+
guard first & 0x7f == 0 else { throw WalletError(.invalidRequest, "unsupported Solana message version") }
|
|
75
|
+
isV0 = true
|
|
76
|
+
first = try r.u8()
|
|
77
|
+
}
|
|
78
|
+
let required = first
|
|
79
|
+
_ = try r.u8() // readonly signed
|
|
80
|
+
_ = try r.u8() // readonly unsigned
|
|
81
|
+
let keyCount = try r.len()
|
|
82
|
+
var keys = [String]()
|
|
83
|
+
for _ in 0..<keyCount { keys.append(Base58.encode(try r.take(32), alphabet: Base58.bitcoin)) }
|
|
84
|
+
let blockhash = Base58.encode(try r.take(32), alphabet: Base58.bitcoin)
|
|
85
|
+
var raw = [(program: Int, accounts: [Int], data: [UInt8])]()
|
|
86
|
+
for _ in 0..<(try r.len()) {
|
|
87
|
+
let program = try r.u8()
|
|
88
|
+
var accounts = [Int]()
|
|
89
|
+
for _ in 0..<(try r.len()) { accounts.append(try r.u8()) }
|
|
90
|
+
let data = try r.take(try r.len())
|
|
91
|
+
raw.append((program, accounts, data))
|
|
92
|
+
}
|
|
93
|
+
var lookups = 0
|
|
94
|
+
if isV0 {
|
|
95
|
+
for _ in 0..<(try r.len()) {
|
|
96
|
+
_ = try r.take(32)
|
|
97
|
+
lookups += try r.take(try r.len()).count
|
|
98
|
+
lookups += try r.take(try r.len()).count
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
guard r.done, required >= 1, required <= keyCount else { throw invalid() }
|
|
102
|
+
let total = keyCount + lookups
|
|
103
|
+
func key(_ i: Int) throws -> String? {
|
|
104
|
+
guard i < total else { throw invalid() }
|
|
105
|
+
return i < keyCount ? keys[i] : nil
|
|
106
|
+
}
|
|
107
|
+
let instructions = try raw.map { ix -> Instruction in
|
|
108
|
+
// Programs must be static keys.
|
|
109
|
+
guard let program = try key(ix.program) else { throw invalid() }
|
|
110
|
+
return Instruction(programId: program, accounts: try ix.accounts.map(key), data: ix.data)
|
|
111
|
+
}
|
|
112
|
+
return Message(isV0: isV0, numRequiredSignatures: required, accountKeys: keys, recentBlockhash: blockhash, instructions: instructions, lookupAccounts: lookups)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private static func u64(_ d: [UInt8], _ at: Int) throws -> UInt64 {
|
|
116
|
+
guard d.count >= at + 8 else { throw invalid() }
|
|
117
|
+
return (0..<8).reduce(UInt64(0)) { $0 | UInt64(d[at + $1]) << (8 * UInt64($1)) }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private static func u32(_ d: [UInt8], _ at: Int) throws -> UInt32 {
|
|
121
|
+
guard d.count >= at + 4 else { throw invalid() }
|
|
122
|
+
return (0..<4).reduce(UInt32(0)) { $0 | UInt32(d[at + $1]) << (8 * UInt32($1)) }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Integer amount with `decimals` places, trailing zeros removed.
|
|
126
|
+
static func units(_ amount: UInt64, _ decimals: Int) -> String {
|
|
127
|
+
if decimals == 0 { return String(amount) }
|
|
128
|
+
var digits = String(amount)
|
|
129
|
+
if digits.count <= decimals { digits = String(repeating: "0", count: decimals - digits.count + 1) + digits }
|
|
130
|
+
let whole = String(digits.dropLast(decimals))
|
|
131
|
+
var frac = String(digits.suffix(decimals))
|
|
132
|
+
while frac.hasSuffix("0") { frac.removeLast() }
|
|
133
|
+
return whole + (frac.isEmpty ? "" : "." + frac)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private static func addr(_ a: String??) -> String { (a ?? nil) ?? "(address from a lookup table)" }
|
|
137
|
+
|
|
138
|
+
/// The approval summary. The signer must pay the fee (account 0).
|
|
139
|
+
static func summarize(_ msg: Message, signer: String, network: String) throws -> SignSummary {
|
|
140
|
+
guard msg.accountKeys.first == signer else {
|
|
141
|
+
throw WalletError(.addressMismatch, "the fee payer is not the signing account")
|
|
142
|
+
}
|
|
143
|
+
var rows: [SignSummary.Row] = [.init(label: "Network", value: network), .init(label: "Account", value: signer, mono: true)]
|
|
144
|
+
var warnings = [String]()
|
|
145
|
+
if msg.numRequiredSignatures > 1 {
|
|
146
|
+
warnings.append("This transaction needs \(msg.numRequiredSignatures) signatures; you sign as the fee payer.")
|
|
147
|
+
}
|
|
148
|
+
if msg.lookupAccounts > 0 { warnings.append("Some accounts come from address lookup tables and cannot be shown.") }
|
|
149
|
+
for ix in msg.instructions {
|
|
150
|
+
let d = ix.data
|
|
151
|
+
func account(_ i: Int) -> String?? { i < ix.accounts.count ? ix.accounts[i] : .some(nil) }
|
|
152
|
+
let isToken = ix.programId == token || ix.programId == token2022
|
|
153
|
+
if ix.programId == system, d.count >= 12, try u32(d, 0) == 2 {
|
|
154
|
+
rows += [.init(label: "Send", value: units(try u64(d, 4), 9) + " SOL"), .init(label: "To", value: addr(account(1)), mono: true)]
|
|
155
|
+
if (ix.accounts.first ?? nil) != signer { warnings.append("SOL is sent from an account other than yours.") }
|
|
156
|
+
} else if isToken, d.first == 12, d.count >= 10 {
|
|
157
|
+
rows += [
|
|
158
|
+
.init(label: "Send token", value: units(try u64(d, 1), Int(d[9]))),
|
|
159
|
+
.init(label: "Token", value: addr(account(1)), mono: true),
|
|
160
|
+
.init(label: "To token account", value: addr(account(2)), mono: true),
|
|
161
|
+
]
|
|
162
|
+
} else if isToken, d.first == 3, d.count >= 9 {
|
|
163
|
+
rows += [
|
|
164
|
+
.init(label: "Send token (raw units)", value: String(try u64(d, 1))),
|
|
165
|
+
.init(label: "To token account", value: addr(account(1)), mono: true),
|
|
166
|
+
]
|
|
167
|
+
warnings.append("Token amount is shown in raw units (no decimals in this instruction).")
|
|
168
|
+
} else if ix.programId == associatedToken, d.isEmpty || d[0] == 0 || d[0] == 1 {
|
|
169
|
+
rows.append(.init(label: "Create token account", value: addr(account(1)), mono: true))
|
|
170
|
+
} else if ix.programId == computeBudget, d.first == 2, d.count >= 5 {
|
|
171
|
+
rows.append(.init(label: "Compute limit", value: String(try u32(d, 1))))
|
|
172
|
+
} else if ix.programId == computeBudget, d.first == 3, d.count >= 9 {
|
|
173
|
+
rows.append(.init(label: "Priority fee", value: "\(try u64(d, 1)) micro-lamports per unit"))
|
|
174
|
+
} else if ix.programId == memo {
|
|
175
|
+
rows.append(.init(label: "Memo", value: String(decoding: d, as: UTF8.self)))
|
|
176
|
+
} else {
|
|
177
|
+
rows.append(.init(label: "Program call", value: ix.programId, mono: true))
|
|
178
|
+
warnings.append("Calls a program this wallet cannot describe (\(ix.programId)). Approve only if you trust the app.")
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
rows.append(.init(label: "Recent blockhash", value: msg.recentBlockhash, mono: true))
|
|
182
|
+
return SignSummary(kind: .transaction, title: "Solana transaction", rows: rows, body: nil, warnings: warnings, risk: .high)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// The SLIP-0010 key of Solana account `index`. Callers wipe it.
|
|
186
|
+
static func privateKey(_ secret: [UInt8], index: Int) throws -> [UInt8] {
|
|
187
|
+
guard WalletShares.isAccountIndex(index) else { throw WalletError(.invalidRequest, "invalid account index") }
|
|
188
|
+
guard WalletShares.isHdSecret(secret) else { throw WalletError(.accountUnavailable, "this wallet has only EVM account 0") }
|
|
189
|
+
var seed = try BIP39.seed(mnemonic: try BIP39.mnemonic(entropy: secret))
|
|
190
|
+
defer { seed.wipe() }
|
|
191
|
+
return try ChainAddresses.ed25519Key(seed: seed, path: WalletChain.solana.path(index))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Deterministic RFC 8032 Ed25519 (Monocypher) over the exact message bytes.
|
|
195
|
+
/// Returns (address, base58 signature).
|
|
196
|
+
static func sign(_ message: [UInt8], privateKey: [UInt8]) -> (address: String, signature: String) {
|
|
197
|
+
var seed = privateKey // crypto_ed25519_key_pair wipes its seed argument
|
|
198
|
+
var secretKey = [UInt8](repeating: 0, count: 64)
|
|
199
|
+
var publicKey = [UInt8](repeating: 0, count: 32)
|
|
200
|
+
var signature = [UInt8](repeating: 0, count: 64)
|
|
201
|
+
crypto_ed25519_key_pair(&secretKey, &publicKey, &seed)
|
|
202
|
+
message.withUnsafeBufferPointer { m in crypto_ed25519_sign(&signature, secretKey, m.baseAddress, m.count) }
|
|
203
|
+
secretKey.wipe()
|
|
204
|
+
seed.wipe()
|
|
205
|
+
return (Base58.encode(publicKey, alphabet: Base58.bitcoin), Base58.encode(signature, alphabet: Base58.bitcoin))
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Chains/Tron.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// TRON transaction signing (docs/wallet/NATIVE.md section 12, `apps/wallet/src/tron.ts`). The app
|
|
5
|
+
/// builds raw_data; the wallet decodes it only to describe it and signs SHA-256(raw_data) like
|
|
6
|
+
/// TronWeb (r || s || v with v = recovery + 27).
|
|
7
|
+
enum Tron {
|
|
8
|
+
static let maxRawData = 4096
|
|
9
|
+
|
|
10
|
+
struct Contract {
|
|
11
|
+
var type: UInt64
|
|
12
|
+
/// Decoded fields of TransferContract (1) / TriggerSmartContract (31).
|
|
13
|
+
var owner: String?
|
|
14
|
+
var to: String?
|
|
15
|
+
var amount: UInt64?
|
|
16
|
+
var contractAddress: String?
|
|
17
|
+
var callValue: UInt64?
|
|
18
|
+
var data: [UInt8]?
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
struct RawData {
|
|
22
|
+
var expiration: UInt64?
|
|
23
|
+
var timestamp: UInt64?
|
|
24
|
+
var feeLimit: UInt64?
|
|
25
|
+
var memo: [UInt8]?
|
|
26
|
+
var contracts: [Contract]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private static func invalid() -> WalletError { WalletError(.invalidRequest, "not a valid TRON raw_data") }
|
|
30
|
+
|
|
31
|
+
struct Field {
|
|
32
|
+
var no: UInt64
|
|
33
|
+
var varint: UInt64?
|
|
34
|
+
var bytes: [UInt8]?
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Minimal protobuf reader: varint (0), 64-bit (1), length-delimited (2), 32-bit (5).
|
|
38
|
+
static func fields(_ buf: [UInt8]) throws -> [Field] {
|
|
39
|
+
var out = [Field]()
|
|
40
|
+
var pos = 0
|
|
41
|
+
func varint() throws -> UInt64 {
|
|
42
|
+
var v: UInt64 = 0
|
|
43
|
+
var shift: UInt64 = 0
|
|
44
|
+
while shift < 64 {
|
|
45
|
+
guard pos < buf.count else { throw invalid() }
|
|
46
|
+
let b = buf[pos]
|
|
47
|
+
pos += 1
|
|
48
|
+
if shift == 63 && b > 1 { throw invalid() }
|
|
49
|
+
v |= UInt64(b & 0x7f) << shift
|
|
50
|
+
if b & 0x80 == 0 { return v }
|
|
51
|
+
shift += 7
|
|
52
|
+
}
|
|
53
|
+
throw invalid()
|
|
54
|
+
}
|
|
55
|
+
while pos < buf.count {
|
|
56
|
+
let key = try varint()
|
|
57
|
+
let no = key >> 3
|
|
58
|
+
let wire = key & 7
|
|
59
|
+
guard no != 0 else { throw invalid() }
|
|
60
|
+
switch wire {
|
|
61
|
+
case 0:
|
|
62
|
+
out.append(Field(no: no, varint: try varint()))
|
|
63
|
+
case 2:
|
|
64
|
+
let len = try varint()
|
|
65
|
+
guard len <= UInt64(buf.count - pos) else { throw invalid() }
|
|
66
|
+
out.append(Field(no: no, bytes: Array(buf[pos..<(pos + Int(len))])))
|
|
67
|
+
pos += Int(len)
|
|
68
|
+
case 1, 5:
|
|
69
|
+
let len = wire == 1 ? 8 : 4
|
|
70
|
+
guard pos + len <= buf.count else { throw invalid() }
|
|
71
|
+
pos += len
|
|
72
|
+
out.append(Field(no: no))
|
|
73
|
+
default:
|
|
74
|
+
throw invalid()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private static func one(_ fs: [Field], _ no: UInt64) -> Field? { fs.first { $0.no == no } }
|
|
81
|
+
|
|
82
|
+
static func address(_ raw: [UInt8]) throws -> String {
|
|
83
|
+
guard raw.count == 21, raw[0] == 0x41 else { throw invalid() }
|
|
84
|
+
return Base58.encode(raw + ChainAddresses.checksum4(raw), alphabet: Base58.bitcoin)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static func parse(_ raw: [UInt8]) throws -> RawData {
|
|
88
|
+
guard !raw.isEmpty, raw.count <= maxRawData else { throw invalid() }
|
|
89
|
+
let top = try fields(raw)
|
|
90
|
+
let contracts = try top.filter { $0.no == 11 && $0.bytes != nil }.map { f -> Contract in
|
|
91
|
+
let c = try fields(f.bytes!)
|
|
92
|
+
let type = one(c, 1)?.varint ?? 0
|
|
93
|
+
var contract = Contract(type: type)
|
|
94
|
+
let any = one(c, 2)?.bytes
|
|
95
|
+
let value = try any.flatMap { one(try fields($0), 2)?.bytes }
|
|
96
|
+
if let value, type == 1 || type == 31 {
|
|
97
|
+
let v = try fields(value)
|
|
98
|
+
func addr(_ no: UInt64) throws -> String? { try one(v, no)?.bytes.map(address) }
|
|
99
|
+
contract.owner = try addr(1)
|
|
100
|
+
if type == 1 {
|
|
101
|
+
contract.to = try addr(2)
|
|
102
|
+
contract.amount = one(v, 3)?.varint ?? 0
|
|
103
|
+
} else {
|
|
104
|
+
contract.contractAddress = try addr(2)
|
|
105
|
+
contract.callValue = one(v, 3)?.varint ?? 0
|
|
106
|
+
contract.data = one(v, 4)?.bytes ?? []
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return contract
|
|
110
|
+
}
|
|
111
|
+
guard !contracts.isEmpty else { throw invalid() }
|
|
112
|
+
return RawData(
|
|
113
|
+
expiration: one(top, 8)?.varint,
|
|
114
|
+
timestamp: one(top, 14)?.varint,
|
|
115
|
+
feeLimit: one(top, 18)?.varint,
|
|
116
|
+
memo: one(top, 10)?.bytes,
|
|
117
|
+
contracts: contracts
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static func trx(_ sun: UInt64) -> String { Solana.units(sun, 6) + " TRX" }
|
|
122
|
+
|
|
123
|
+
private static func word(_ data: [UInt8], _ i: Int) -> [UInt8] { Array(data[(4 + 32 * i)..<(4 + 32 * (i + 1))]) }
|
|
124
|
+
|
|
125
|
+
/// `new Date(ms).toISOString()`
|
|
126
|
+
static func isoDate(_ ms: UInt64) throws -> String {
|
|
127
|
+
guard ms <= 8_640_000_000_000_000 else { throw invalid() }
|
|
128
|
+
let f = ISO8601DateFormatter()
|
|
129
|
+
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
130
|
+
f.timeZone = TimeZone(identifier: "UTC")
|
|
131
|
+
return f.string(from: Date(timeIntervalSince1970: Double(ms) / 1000))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static func summarize(_ tx: RawData, signer: String) throws -> SignSummary {
|
|
135
|
+
var rows: [SignSummary.Row] = [.init(label: "Network", value: "TRON"), .init(label: "Account", value: signer, mono: true)]
|
|
136
|
+
var warnings = [String]()
|
|
137
|
+
if tx.contracts.count != 1 { warnings.append("This transaction has \(tx.contracts.count) contracts.") }
|
|
138
|
+
for c in tx.contracts {
|
|
139
|
+
if let owner = c.owner, owner != signer {
|
|
140
|
+
throw WalletError(.addressMismatch, "the transaction owner is not the signing account")
|
|
141
|
+
}
|
|
142
|
+
if c.type == 1, let to = c.to, !to.isEmpty {
|
|
143
|
+
rows += [.init(label: "Send", value: trx(c.amount ?? 0)), .init(label: "To", value: to, mono: true)]
|
|
144
|
+
} else if c.type == 31, let data = c.data {
|
|
145
|
+
rows.append(.init(label: "Contract", value: c.contractAddress ?? "?", mono: true))
|
|
146
|
+
if let v = c.callValue, v > 0 { rows.append(.init(label: "Send", value: trx(v))) }
|
|
147
|
+
let selector = Bytes.hex(Array(data.prefix(4)))
|
|
148
|
+
if selector == "a9059cbb" && data.count == 68 {
|
|
149
|
+
rows += [
|
|
150
|
+
.init(label: "Token transfer to", value: try address([0x41] + word(data, 0)[12...]), mono: true),
|
|
151
|
+
.init(label: "Token amount (raw units)", value: BigUInt(bytes: word(data, 1)).description),
|
|
152
|
+
]
|
|
153
|
+
} else if selector == "095ea7b3" && data.count == 68 {
|
|
154
|
+
let amount = BigUInt(bytes: word(data, 1))
|
|
155
|
+
rows += [
|
|
156
|
+
.init(label: "Allow spender", value: try address([0x41] + word(data, 0)[12...]), mono: true),
|
|
157
|
+
.init(label: "Allowance (raw units)", value: amount == BigUInt.power2(256) - BigUInt(1) ? "Unlimited" : amount.description),
|
|
158
|
+
]
|
|
159
|
+
warnings.append("This lets another address spend your tokens.")
|
|
160
|
+
} else {
|
|
161
|
+
rows.append(.init(label: "Call data", value: Bytes.hex(data, prefix: true), mono: true))
|
|
162
|
+
warnings.append("Calls a contract function this wallet cannot describe. Approve only if you trust the app.")
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
rows.append(.init(label: "Contract type", value: String(c.type)))
|
|
166
|
+
warnings.append("TRON contract type \(c.type) cannot be described. Approve only if you trust the app.")
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if let fee = tx.feeLimit { rows.append(.init(label: "Fee limit", value: trx(fee))) }
|
|
170
|
+
if let memo = tx.memo, !memo.isEmpty { rows.append(.init(label: "Memo", value: String(decoding: memo, as: UTF8.self))) }
|
|
171
|
+
if let exp = tx.expiration { rows.append(.init(label: "Expires", value: try isoDate(exp))) }
|
|
172
|
+
return SignSummary(kind: .transaction, title: "TRON transaction", rows: rows, body: nil, warnings: warnings, risk: .high)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// txID = SHA-256(raw_data); signature r || s || (recovery + 27) in lowercase hex.
|
|
176
|
+
static func sign(_ raw: [UInt8], privateKey: [UInt8]) throws -> [String: String] {
|
|
177
|
+
let txID = Bytes.sha256(raw)
|
|
178
|
+
let sig = try Secp256k1.sign(hash: txID, key: privateKey)
|
|
179
|
+
return ["txID": Bytes.hex(txID), "signature": Bytes.hex(sig.r + sig.s + [UInt8(27 + sig.recoveryId)])]
|
|
180
|
+
}
|
|
181
|
+
}
|