【问题标题】:Big number computation in Swift for RSA implementation用于 RSA 实现的 Swift 中的大数计算
【发布时间】:2021-10-23 06:37:59
【问题描述】:

我正在尝试在 Swift 中为 CryptoSwift 库实现 RSA 算法(以修复 #63)。该算法本身正在运行,但我需要提高大数计算性能才能使其在合理的时间内运行。

我实现了自己的GiantUInt 结构(将字节存储为UInt8)来计算RSA 大数(例如2048 位长度)的操作,但它太慢了(主要是余数操作,但我认为一切都可以改进):

precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator ^^ : PowerPrecedence

public struct GiantUInt: Equatable, Comparable, ExpressibleByIntegerLiteral, ExpressibleByArrayLiteral {
  
  // Properties
  
  public let bytes: Array<UInt8>
  
  // Initialization
  
  public init(_ raw: Array<UInt8>) {
    var bytes = raw
    
    while bytes.last == 0 {
      bytes.removeLast()
    }
    
    self.bytes = bytes
  }
  
  // ExpressibleByIntegerLiteral
  
  public typealias IntegerLiteralType = UInt8
  
  public init(integerLiteral value: UInt8) {
    self = GiantUInt([value])
  }
  
  // ExpressibleByArrayLiteral
  
  public typealias ArrayLiteralElement = UInt8
  
  public init(arrayLiteral elements: UInt8...) {
    self = GiantUInt(elements)
  }
    
  // Equatable
  
  public static func == (lhs: GiantUInt, rhs: GiantUInt) -> Bool {
    lhs.bytes == rhs.bytes
  }
  
  // Comparable
  
  public static func < (rhs: GiantUInt, lhs: GiantUInt) -> Bool {
    for i in (0 ..< max(rhs.bytes.count, lhs.bytes.count)).reversed() {
      let r = rhs.bytes[safe: i] ?? 0
      let l = lhs.bytes[safe: i] ?? 0
      if r < l {
        return true
      } else if r > l {
        return false
      }
    }
    
    return false
  }
  
  // Operations
  
  public static func + (rhs: GiantUInt, lhs: GiantUInt) -> GiantUInt {
    var bytes = [UInt8]()
    var r: UInt8 = 0
    
    for i in 0 ..< max(rhs.bytes.count, lhs.bytes.count) {
      let res = UInt16(rhs.bytes[safe: i] ?? 0) + UInt16(lhs.bytes[safe: i] ?? 0) + UInt16(r)
      r = UInt8(res >> 8)
      bytes.append(UInt8(res & 0xff))
    }
    
    if r != 0 {
      bytes.append(r)
    }
    
    return GiantUInt(bytes)
  }
  
  public static func - (rhs: GiantUInt, lhs: GiantUInt) -> GiantUInt {
    var bytes = [UInt8]()
    var r: UInt8 = 0
    
    for i in 0 ..< max(rhs.bytes.count, lhs.bytes.count) {
      let rhsb = UInt16(rhs.bytes[safe: i] ?? 0)
      let lhsb = UInt16(lhs.bytes[safe: i] ?? 0) + UInt16(r)
      r = UInt8(rhsb < lhsb ? 1 : 0)
      let res = (UInt16(r) << 8) + rhsb - lhsb
      bytes.append(UInt8(res & 0xff))
    }
    
    if r != 0 {
      bytes.append(r)
    }
    
    return GiantUInt(bytes)
  }
  
  public static func * (rhs: GiantUInt, lhs: GiantUInt) -> GiantUInt {
    var offset = 0
    var sum = [GiantUInt]()
    
    for rbyte in rhs.bytes {
      var bytes = [UInt8](repeating: 0, count: offset)
      var r: UInt8 = 0
      
      for lbyte in lhs.bytes {
        let res = UInt16(rbyte) * UInt16(lbyte) + UInt16(r)
        r = UInt8(res >> 8)
        bytes.append(UInt8(res & 0xff))
      }
      
      if r != 0 {
        bytes.append(r)
      }
      
      sum.append(GiantUInt(bytes))
      offset += 1
    }
    
    return sum.reduce(0, +)
  }
  
  public static func % (rhs: GiantUInt, lhs: GiantUInt) -> GiantUInt {
    var remainder = rhs
    
    // This needs serious optimization (but works)
    while remainder >= lhs {
      remainder = remainder - lhs
    }
  
    return remainder
  }
  
  static func ^^ (rhs: GiantUInt, lhs: GiantUInt) -> GiantUInt {
    let count = lhs.bytes.count
    var result = GiantUInt([1])
    
    for iByte in 0 ..< count {
      let byte = lhs.bytes[iByte]
      for i in 0 ..< 8 {
        if iByte != count - 1 || byte >> i > 0 {
          result = result * result
          if (byte >> i) & 1 == 1 {
            result = result * rhs
          }
        }
      }
    }
    
    return result
  }
  
  public static func exponentiateWithModulus(rhs: GiantUInt, lhs: GiantUInt, modulus: GiantUInt) -> GiantUInt {
    let count = lhs.bytes.count
    var result = GiantUInt([1])
    
    for iByte in 0 ..< count {
      let byte = lhs.bytes[iByte]
      for i in 0 ..< 8 {
        if iByte != count - 1 || byte >> i > 0 {
          result = (result * result) % modulus
          if (byte >> i) & 1 == 1 {
            result = (result * rhs) % modulus
          }
        }
      }
    }
    
    return result
  }
  
}

(此文件可用here on my fork

我怎样才能改进它以使其(很多)更快?

【问题讨论】:

  • 我想先得到商,但我不知道如何处理这些数字
  • 考虑检查Swift-BigInt

标签: swift optimization rsa mathematical-optimization bigint


【解决方案1】:

我怎样才能改进它以使其(很多)更快?

不要使用字节。性能取决于大数字中“数字”的数量;所以更多的小数字更糟,更少的大数字更好。例如,对于一对 2048 位大数相乘,如果它是使用字节实现的,那么您最终会得到“256 位 * 256 位 = 65536 位数字乘法”,如果它是使用 64 位整数实现的,那么您最终会得到“ 32 位 * 32 位 = 1024 位乘法”(大约快 64 倍)。

更喜欢破坏性操作

对于大数字;对于类似“a = b + c”的情况,CPU 必须处理 3 组缓存线,而对于类似“a += b”的情况,CPU 只需处理 2 组缓存线。对于大的数字,这可能是“它都适合缓存”和“性能被缓存未命中破坏”之间的区别。

不要使用附加

bytes.append(r) 之类的事情可能涉及缓冲区容量检查和底层缓冲区的潜在调整大小;而且这种额外的开销是不必要且可以避免的——您应该能够提前确定结果的大小,提前创建一个正确大小的数组,然后在不进行任何检查和调整大小的情况下计算结果。

不要使用乘法进行平方

对于平方,“数字乘法”的数量几乎可以减少一半,因为这两个数字都是相同的数字。为了理解这一点,假设你用十进制表示 1234 * 1234 并将中间值表示为这样的网格:

     1        2         3           4
    --------------
 1 | 1*1    + 2*10    + 3*100     + 4*1000 +
 2 | 2*10   + 4*100   + 6*1000    + 8*10000 +
 3 | 3*100  + 6*1000  + 9*10000   + 12*100000 +
 4 | 4*1000 + 8*10000 + 12*100000 + 16*1000000

您可以看到右上角的“不完全一半”网格是左下角“不完全一半”的镜像,因此您可以这样做:

     1          2           3              4
    --------------
 1 | 1*1
 2 | (2*10)*2 + 4*100
 3 | (3*100   + 6*1000)*2 + 9*10000
 4 | (4*1000  + 8*10000   + 12*100000)*2 + 16*1000000

当然,这可以重新排列,使*2 只发生一次;比如“result = (2*10 + 3*100 + 6*1000 + 4*1000 + 8*10000 + 12*100000) * 2 + 1*1 + 4*100 + 9*10000 + 16*1000000”。

您的模数可以/应该改进

一种方法是将除数向左移动,直到它大于分子(同时跟踪移位计数);然后执行“当移位计数不为零时{右移位除数;如果除数不大于分子,则从分子中减去除数;减少移位计数}”。

不要使用 Swift 或高级语言

大多数 CPU 都有特殊指令,可以更高效地处理大数,在大多数高级语言中,即使是基本的东西(例如“带进位相加”)也是不可能的。结果是使用最佳数字大小(例如 64 位 CPU 上的 64 位数字)很痛苦,因此您实现算法的方式不同,然后编译器无法正确优化,因为算法不同。

只有使用汇编语言才能获得最佳性能(例如,可以从 swift 代码中使用的本机库)。如果您将(例如)GMP 库(使用大量内联汇编语言)与 mini-GMP(不使用汇编语言并且仅旨在“数字速度不超过 10 倍)进行比较,您可以清楚地看到这种差异最多几百位”)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    • 2011-02-02
    • 1970-01-01
    • 2018-11-22
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多