K1 🏔

比K2更安全

K1 是对 libsecp256k1 (bitcoin-core/secp256k1) 的 Swift 封装,提供 ECDSA、Schnorr (BIP340) 和 ECDH 功能。

文档

在此处阅读完整文档 SwiftPackageIndex

快速概览

K1 的 API 几乎与 Apple 的 CryptoKit 1:1 映射,为每个功能提供一组密钥对。 例如,在 CryptoKit 中,你有 Curve25519.KeyAgreement.PrivateKeyCurve25519.KeyAgreement.PublicKey,它们与 Curve25519.Signing.PrivateKeyCurve25519.Signing.PublicKey 是分开的。

就像这样,K1 提供这些密钥对

就像你可以使用任何初始化器和序列化器在 Curve25519.KeyAgreement.PrivateKeyCurve25519.Signing.PrivateKey 之间来回转换一样,你可以在 K1 中所有功能的 PrivateKey 和 PublicKey 之间进行转换。

所有密钥都可以使用这些计算属性进行序列化

{
    var rawRepresentation: Data { get }
    var derRepresentation: Data { get }
    var pemRepresentation: String { get }
    var x963Representation: Data { get }
}

所有密钥都可以使用这些初始化器进行反序列化

{
    init(rawRepresentation: some ContiguousBytes) throws
    init(derRepresentation: some RandomAccessCollection<UInt8>) throws
    init(pemRepresentation: String) throws
    init(x963Representation: some ContiguousBytes) throws
}

此外,所有 PrivateKey 都有这些额外的 API

{
    init()
    associatedtype PublicKey
    var publicKey: PublicKey { get }
}

此外,所有 PublicKey 都有这些额外的 API

{
    init(compressedRepresentation: some ContiguousBytes) throws
    var compressedRepresentation: Data { get }
}

ECDSA(椭圆曲线数字签名算法)

存在两组 ECDSA 密钥对

对于每个私钥,存在两种不同的 signature:for:options 方法(一种接受哈希数据,另一种接受 Digest 作为参数)和一种 signature:forUnhashed:options

option 是一个 K1.ECDSA.SigningOptions 结构体,默认情况下指定 RFC6979 确定性签名,根据比特币标准,但是,您可以更改为使用安全随机 nonce 代替。

不可恢复

签名

let alice = K1.ECDSA.PrivateKey()
哈希(数据)
let hashedMessage: Data = // from somewhere
let signature = try alice.signature(for: hashedMessage)
摘要
let message: Data = // from somewhere
let digest = SHA256.hash(data: message)
let signature = try alice.signature(for: digest)
哈希和签名

forUnhashed 将对消息进行 SHA256 哈希,然后对其进行签名。

let message: Data = // from somewhere
let signature = try alice.signature(forUnhashed: message)

验证

哈希(数据)
let hashedMessage: Data = // from somewhere
let publicKey: K1.ECDSA.PublicKey = alice.publcKey
let signature: K1.ECDSA.Signature // from above

assert(
    publicKey.isValidSignature(signature, hashed: hashedMessage)
) // PASS
摘要
let message: Data = // from somewhere
let digest = SHA256.hash(data: message)
let signature: K1.ECDSA.Signature // from above

assert(
    publicKey.isValidSignature(signature, digest: digest)
) // PASS
哈希和验证
let message: Data = // from somewhere
let signature: K1.ECDSA.Signature // from above

assert(
    publicKey.isValidSignature(signature, unhashed: message)
) // PASS

可恢复

所有签名和验证 API 与 NonRecoverable 命名空间相同。

let alice = K1.ECDSA.PrivateKey()
let message: Data = // from somewhere
let digest = SHA256.hash(data: message)
let signature: K1.ECDSAWithKeyRecovery.Signature = try alice.signature(for: digest)
let publicKey: K1.ECDSAWithKeyRecovery.PublicKey = alice.publicKey
assert(
    publicKey.isValidSignature(signature, digest: digest)
) // PASS

Schnorr 签名方案

签名

let alice = K1.Schnorr.PrivateKey()
let signature = try alice.signature(forUnhashed: message)

存在其他签名变体,signature:for:options(哈希数据)和 signature:for:options (Digest),如果您已经有一个哈希消息。 所有三种变体都采用一个 K1.Schnorr.SigningOptions 结构,您可以在其中传递 auxiliaryRandomData 进行签名。

验证

let publicKey: K1.Schnorr.PublicKey = alice.publicKey
assert(publicKey.isValidSignature(signature, unhashed: message)) // PASS

或者,可选地 isValidSignature:digestisValidSignature:hashed

Schnorr 方案

Schnorr 签名实现是 BIP340,因为我们使用 libsecp256k1,它只提供 BIP340 Schnorr 方案。

值得注意的是,某些 Schnorr 实现与 BIP340 不兼容,因此与此库不兼容,例如 Zilliqa 的 ( kudelski 报告, libsecp256k1 提案, Twitter 帖子)。

ECDH

该库提供三种不同的 EC Diffie-Hellman (ECDH) 密钥交换函数

  1. ASN1 x9.63 - 无哈希,仅返回点的 X 坐标 - sharedSecretFromKeyAgreement:with -> SharedSecret
  2. libsecp256k1 - SHA-256 哈希压缩点 - ecdh:with -> SharedSecret
  3. 自定义 - 无哈希,返回未压缩的点 - ecdhPoint -> Data
let alice = try K1.KeyAgreement.PrivateKey()
let bob = try K1.KeyAgreement.PrivateKey()

ASN1 x9.63 ECDH

仅返回点的 X 坐标,遵循 ANSI X9.63 标准,嵌入在 CryptoKit.SharedSecret 中,这很有用,因为您可以在此 SharedSecret 上使用 CryptoKit 密钥派生函数,例如 x963DerivedSymmetricKeyhkdfDerivedSymmetricKey

如果您需要,可以使用 withUnsafeBytes 以原始数据的形式检索 X 坐标。

let ab: CryptoKit.SharedSecret = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey) 
let ba: CryptoKit.SharedSecret = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)

assert(ab == ba) // pass

ab.withUnsafeBytes {
    assert(Data($0).count == 32) // pass
}

libsecp256k1 ECDH

使用 libsecp256k1 默认行为,返回 **压缩** 点的 SHA-256 哈希,嵌入在 CryptoKit.SharedSecret 中,这很有用,因为您可以使用 CryptoKit 密钥派生函数。

let ab: CryptoKit.SharedSecret = try alice.ecdh(with: bob.publicKey) 
let ba: CryptoKit.SharedSecret = try bob.ecdh(with: alice.publicKey)
assert(ab == ba) // pass

ab.withUnsafeBytes {
    assert(Data($0).count == 32) // pass
}

自定义 ECDH

返回整个未压缩的 EC 点,而不对其进行哈希处理。 如果你想构建自己的密码函数(例如一些自定义 ECIES),这可能很有用。

let ab: Data = try alice.ecdhPoint(with: bob.publicKey) 
let ba: Data = try bob.ecdhPoint(with: alice.publicKey)
assert(ab == ba) // pass

assert(ab.count == 65) // pass

致谢

K1libsecp256k1 的 Swift 封装,因此如果没有 Bitcoin Core 开发人员,就不会有这个库。 非常感谢你提供了一个出色的库! 我已将其作为子模块包含在内,而没有对代码进行任何更改,即文件中的版权标头保持不变。

K1 使用了 swift-crypto 中的一些代码,这些代码已复制并带有相关的版权标头。 由于 swift-crypto 是在 Apache 下获得许可的,因此该库也是如此。

开发

进入根目录并运行

./scripts/build.sh

要克隆依赖项 libsecp256k1,使用提交 427bc3cdcfbc74778070494daab1ae5108c71368 (semver 0.3.0)

gyb

该项目中的某些文件是使用名为 gyb ("generate your boilerplate") 的 Swift Utils 工具自动生成的(元编程)。 gyb 包含在 ./scripts/gyb 中。

gyb 将从某些 Foobar.swift.gyb *模板* 文件生成一些 Foobar.swift Swift 文件。 **你不应该直接编辑 Foobar.swift**,因为下次运行 gyb 时,该生成文件中的所有手动编辑都将被覆盖。

你可以像这样为单个文件运行 gyb

./scripts/gyb --line-directive "" Sources/Foobar.swift.gyb -o Sources/Foobar.swift

更方便的是,你可以运行 bash 脚本 ./scripts/generate_boilerplate_files_with_gyb.sh 以从其相应的 gyb 模板生成所有 Swift 文件。

**如果你添加一个新的 .gyb 文件,你应该在其内部附加一个 // MARK: - Generated file, do NOT edit 警告**,例如

// MARK: - Generated file, do NOT edit
// any edits of this file WILL be overwritten and thus discarded
// see section `gyb` in `README` for details.

替代方案