Skip to content

Commit

Permalink
fixed Base58. Updated Numberick
Browse files Browse the repository at this point in the history
  • Loading branch information
ypopovych committed Nov 7, 2023
1 parent 029ab47 commit 18bbdb4
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 110 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let package = Package(
targets: ["SubstrateRPC"]),
],
dependencies: [
.package(url: "https://github.com/oscbyspro/Numberick.git", .upToNextMinor(from: "0.11.0")),
.package(url: "https://github.com/oscbyspro/Numberick.git", .upToNextMinor(from: "0.15.0")),
.package(url: "https://github.com/tesseract-one/xxHash.swift.git", .upToNextMinor(from: "0.1.0")),
.package(url: "https://github.com/tesseract-one/ScaleCodec.swift.git", .upToNextMinor(from: "0.3.1")),
.package(url: "https://github.com/tesseract-one/ContextCodable.swift.git", .upToNextMinor(from: "0.1.0")),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Substrate/Extrinsic/AccountId.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public extension AccountId {
}

var string: String {
SS58.encode(data: raw, format: runtime.addressFormat)
try! SS58.encode(data: raw, format: runtime.addressFormat)
}

func address<A: Address>() throws -> A where A.TAccountId == Self {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Substrate/Extrinsic/PublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public extension PublicKey {
}

@inlinable
func ss58(format: SS58.AddressFormat) -> String {
SS58.encode(data: raw, format: format)
func ss58(format: SS58.AddressFormat) throws -> String {
try SS58.encode(data: raw, format: format)
}

@inlinable
Expand Down
181 changes: 94 additions & 87 deletions Sources/Substrate/Utils/Base58.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,107 +14,114 @@ public enum Base58 {
case invalidByte(UInt8)
}

public static func encode(
_ bytes: [UInt8],
encoder mapper: (UInt8) -> UInt8 = Self._encodeByte,
zeroSymbol: UInt8 = Self._alphabet[0]
) -> String {
var zerosCount = 0
while bytes[zerosCount] == 0 {
zerosCount += 1
}

let bytesCount = bytes.count - zerosCount
let b58Count = ((bytesCount * 138) / 100) + 1
var b58 = [UInt8](repeating: 0, count: b58Count)
var count = 0
public struct Alphabet {
public let alphabet: [UInt8]
public let table: [Int8]

var x = zerosCount
while x < bytesCount {
var carry = Int(bytes[x]), i = 0, j = b58Count - 1
while j > -1 {
if carry != 0 || i < count {
carry += 256 * Int(b58[j])
b58[j] = UInt8(carry % 58)
carry /= 58
i += 1
}
j -= 1
public init(_ alphabet: [UInt8]) {
precondition(alphabet.count == 58, "Alphabet should be 58 elements long")
self.alphabet = alphabet
var table = Array<Int8>(repeating: -1, count: Int(UInt8.max))
for (index, char) in alphabet.enumerated() {
table[Int(char)] = Int8(index)
}
count = i
x += 1
self.table = table
}

// skip leading zeros
var leadingZeros = 0
while b58[leadingZeros] == 0 {
leadingZeros += 1
public init(_ alphabet: String) {
self.init(Array(alphabet.utf8))
}

let result = Data(repeating: zeroSymbol, count: zerosCount) + Data(b58[leadingZeros...]).map(mapper)
return String(data: result, encoding: .ascii)!
@inlinable public var zero: UInt8 { alphabet[0] }
}

public static func decode(
_ string: String,
decoder mapper: (UInt8) -> UInt8? = Self._decodeByte,
zeroSymbol: UInt8 = Self._alphabet[0]
) throws -> Array<UInt8> {
let bytes = Array(string.utf8)

var onesCount = 0

while bytes[onesCount] == zeroSymbol {
onesCount += 1
}

let bytesCount = bytes.count - onesCount
let b58Count = ((bytesCount * 733) / 1000) + 1 - onesCount
var b58 = [UInt8](repeating: 0, count: b58Count)
var count = 0

var x = onesCount
while x < bytesCount {
guard let b58Index = mapper(bytes[x]) else {
throw DecodingError.invalidByte(bytes[x])
}
var carry = Int(b58Index), i = 0, j = b58Count - 1
while j > -1 {
if carry != 0 || i < count {
carry += 58 * Int(b58[j])
b58[j] = UInt8(carry % 256)
carry /= 256
i += 1
public static func encode(_ data: Data, alphabet: Alphabet = Self._alphabet) -> String {
data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
var zerosCount: Int = 0
while bytes[zerosCount] == 0 { zerosCount += 1 }

let b58Count = (((bytes.count - zerosCount) * 138) / 100) + 1

var b58 = Data(repeating: 0, count: b58Count)
return b58.withUnsafeMutableBytes { (b58: UnsafeMutableRawBufferPointer) in
var x = zerosCount
var count = 0

while x < bytes.count {
var carry = Int(bytes[x]), i = 0, j = b58Count - 1
while j > -1 {
if carry != 0 || i < count {
carry += 256 * Int(b58[j])
b58[j] = UInt8(carry % 58)
carry /= 58
i += 1
}
j -= 1
}
count = i
x += 1
}

// skip leading zeros
var leadingZeros = 0
while b58[leadingZeros] == 0 { leadingZeros += 1 }

return alphabet.alphabet.withUnsafeBufferPointer { alphabet in
var result = Data()
result.reserveCapacity(zerosCount + b58Count - leadingZeros)
result.append(Data(repeating: alphabet[0], count: zerosCount))
b58[leadingZeros...].forEach { result.append(alphabet[Int($0)]) }
return String(data: result, encoding: .utf8)!
}
j -= 1
}
count = i
x += 1
}

// skip leading zeros
var leadingZeros = 0
while b58[leadingZeros] == 0 {
leadingZeros += 1
}

return [UInt8](repeating: 0, count: onesCount) + [UInt8](b58[leadingZeros...])
}

public static let _alphabet = [UInt8]("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".utf8)

public static let _decodingTable: [UInt8: UInt8] = {
let pairs = Self._alphabet.enumerated().map { index, char in
(char, UInt8(index))
public static func decode(_ string: String, alphabet: Alphabet = Self._alphabet) throws -> Data {
try Array(string.utf8).withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
var zerosCount = 0
while bytes[zerosCount] == alphabet.zero { zerosCount += 1 }

let b58Count = (((bytes.count - zerosCount) * 733) / 1000) + 1

var b58 = Data(repeating: 0, count: b58Count)
let leadingZeros = try b58.withUnsafeMutableBytes { (b58: UnsafeMutableRawBufferPointer) in
try alphabet.table.withUnsafeBufferPointer { table in
var x = zerosCount
var count = 0

while x < bytes.count {
let b58Index = table[Int(bytes[x])]
guard b58Index >= 0 else { throw DecodingError.invalidByte(bytes[x]) }

var carry = Int(b58Index), i = 0, j = b58Count - 1
while j > -1 {
if carry != 0 || i < count {
carry += 58 * Int(b58[j])
b58[j] = UInt8(carry % 256)
carry /= 256
i += 1
}
j -= 1
}
count = i
x += 1
}

// skip leading zeros
var leadingZeros = 0
while b58[leadingZeros] == 0 { leadingZeros += 1 }
return leadingZeros
}
}

var result = Data()
result.reserveCapacity(zerosCount + b58Count - leadingZeros)
result.append(Data(repeating: 0, count: zerosCount))
result.append(b58[leadingZeros...])
return result
}
return Dictionary(pairs) { (l, _) in l }
}()

public static func _encodeByte(byte: UInt8) -> UInt8 {
byte < Self._alphabet.count ? Self._alphabet[Int(byte)] : .max
}

public static func _decodeByte(char: UInt8) -> UInt8? {
Self._decodingTable[char]
}
public static let _alphabet = Alphabet("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
}
25 changes: 13 additions & 12 deletions Sources/Substrate/Utils/SS58.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ public struct SS58 {
public var rawValue: RawValue { id }

public init?(rawValue: Self.RawValue) {
guard rawValue != Self.reserved46.rawValue && rawValue != Self.reserved47.rawValue else {
guard rawValue != Self.reserved46.rawValue &&
rawValue != Self.reserved47.rawValue &&
rawValue < 16_384
else {
return nil
}
self.init(id: rawValue)
Expand Down Expand Up @@ -186,20 +189,18 @@ public struct SS58 {
return (body.suffix(from: prefixLen), format)
}

public static func encode(data: Data, format: AddressFormat) -> String {
// We mask out the upper two bits of the ident - SS58 Prefix currently only supports 14-bits
let ident = format.id & 0b00111111_11111111
var result = Array<UInt8>()
switch ident {
case 0..<64: result.append(UInt8(ident))
public static func encode(data: Data, format: AddressFormat) throws -> String {
var result = Data()
switch format.id {
case 0..<64: result.append(UInt8(format.id))
case 64..<16_384:
// upper six bits of the lower byte(!)
result.append((UInt8(ident & 0b00000000_11111100) >> 2) | 0b01000000)
result.append((UInt8(format.id & 0b00000000_11111100) >> 2) | 0b01000000)
// lower two bits of the lower byte in the high pos,
// lower bits of the upper byte in the low pos
result.append(UInt8(ident >> 8) | UInt8(ident & 0b00000000_00000011) << 6)
result.append(UInt8(format.id >> 8) | UInt8(format.id & 0b00000000_00000011) << 6)
default:
fatalError("masked out the upper two bits; qed")
throw Error.formatNotAllowed
}
let cLength = data.count > 1 ? Self.defaultEncodeChecksumLength : 1
result.append(contentsOf: data)
Expand Down Expand Up @@ -250,7 +251,7 @@ public struct SS58 {
}

@inlinable
public static func encode(data: Data, format: AddressFormat) -> String {
AddressCodec.encode(data: data, format: format)
public static func encode(data: Data, format: AddressFormat) throws -> String {
try AddressCodec.encode(data: data, format: format)
}
}
2 changes: 1 addition & 1 deletion Substrate.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Pod::Spec.new do |s|
s.dependency 'ContextCodable.swift', '~> 0.1.0'
s.dependency 'xxHash', '~> 0.1.0'
s.dependency 'Serializable.swift', '~> 0.3.1'
s.dependency 'Numberick', '~> 0.11.0'
s.dependency 'Numberick', '~> 0.15.0'

s.test_spec 'SubstrateTests' do |ts|
ts.platforms = base_platforms
Expand Down
2 changes: 1 addition & 1 deletion Tests/KeychainTests/EcdsaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ final class EcdsaTests: XCTestCase {
let oPair = try? EcdsaKeyPair(seed: Data("12345678901234567890123456789012".utf8))
XCTAssertNotNil(oPair)
guard let pair = oPair else { return }
let ss58 = pair.pubKey.ss58(format: .substrate)
let ss58 = try! pair.pubKey.ss58(format: .substrate)
let pub = try? EcdsaPublicKey.from(ss58: ss58)
XCTAssertEqual(pair.pubKey.raw, pub?.0.raw)
XCTAssertEqual(pub?.1, .substrate)
Expand Down
2 changes: 1 addition & 1 deletion Tests/KeychainTests/Ed25519Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ final class Ed25519Tests: XCTestCase {
let oPair = try? Ed25519KeyPair(seed: Data("12345678901234567890123456789012".utf8))
XCTAssertNotNil(oPair)
guard let pair = oPair else { return }
let ss58 = pair.pubKey.ss58(format: .substrate)
let ss58 = try! pair.pubKey.ss58(format: .substrate)
let pub = try? Ed25519PublicKey.from(ss58: ss58)
XCTAssertEqual(pair.pubKey.raw, pub?.0.raw)
XCTAssertEqual(pub?.1, .substrate)
Expand Down
2 changes: 1 addition & 1 deletion Tests/KeychainTests/Sr25519Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ final class Sr25519Tests: XCTestCase {

func testSs58CheckRoundtripWorks() {
let pair = Sr25519KeyPair()
let ss58 = pair.pubKey.ss58(format: .substrate)
let ss58 = try! pair.pubKey.ss58(format: .substrate)
let pub = try? Sr25519PublicKey.from(ss58: ss58)
XCTAssertEqual(pair.pubKey.raw, pub?.0.raw)
XCTAssertEqual(pub?.1, .substrate)
Expand Down
29 changes: 29 additions & 0 deletions Tests/SubstrateTests/Base58Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Base58Tests.swift
//
//
// Created by Yehor Popovych on 07/11/2023.
//

import XCTest
import ScaleCodec
@testable import Substrate

final class Base58Tests: XCTestCase {
func testEncDec() throws {
try encDec(Data(hex: "6c13537af75e5a3b724db7334798f9084c192a2896e0c68a4216151b8e29bb32435f")!)
}

func testEncDecZeroes() throws {
try encDec(Data(hex: "006c13537af75e5a3b724db7334798f9084c192a2896e0c68a4216151b8e29bb32435f")!)
try encDec(Data(hex: "00006c13537af75e5a3b724db7334798f9084c192a2896e0c68a4216151b8e29bb32435f")!)
try encDec(Data(hex: "0000006c13537af75e5a3b724db7334798f9084c192a2896e0c68a4216151b8e29bb32435f")!)
try encDec(Data(hex: "000000006c13537af75e5a3b724db7334798f9084c192a2896e0c68a4216151b8e29bb32435f")!)
}

private func encDec(_ data: Data) throws {
let encoded = Base58.encode(data)
let decoded = try Base58.decode(encoded)
XCTAssertEqual(data.hex(), decoded.hex())
}
}
7 changes: 4 additions & 3 deletions Tests/SubstrateTests/BigIntegerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import XCTest
import ScaleCodec
@testable import Substrate
import NBKCoreKit

final class BigIntegerTests: XCTestCase {
private let jsonEncoder = JSONEncoder()
Expand Down Expand Up @@ -127,7 +128,7 @@ final class BigIntegerTests: XCTestCase {
}

private func compactValues<T>(for: T.Type) -> [(T, String)]
where T: UnsignedInteger & FixedWidthInteger & CompactCodable & DataSerializable, T.UI == T
where T: UnsignedInteger & NBKFixedWidthInteger & CompactCodable & DataSerializable, T.UI == T
{
var values: [(T, String)] = [
(T(0), "00"), (T(1 << 6 - 1), "fc"), (T(1 << 6), "01 01"),
Expand All @@ -150,13 +151,13 @@ final class BigIntegerTests: XCTestCase {
}

private func jsonValues<T>(for: T.Type) -> [(T, String)]
where T: UnsignedInteger & FixedWidthInteger & DataSerializable
where T: UnsignedInteger & NBKFixedWidthInteger & DataSerializable
{
var values: [(T, String)] = []
values.reserveCapacity(((T.bitWidth / 8) + 3) * 2)
let pair = { (val: T) -> (T, String) in
if val <= JSONEncoder.maxSafeInteger {
return (val, val.description(radix: 10))
return (val, val.description(radix: 10, uppercase: false))
} else {
let hex = TrimmedHex(data: val.data(littleEndian: false, trimmed: true))
return (val, "\"\(hex.string)\"")
Expand Down

0 comments on commit 18bbdb4

Please sign in to comment.