The API of K1 maps almost 1:1 with Apple’s CryptoKit, vendoring a set of keypairs, one per feature. E.g. in CryptoKit you have Curve25519.KeyAgreement.PrivateKey and Curve25519.KeyAgreement.PublicKey which are seperate for Curve25519.Signing.PrivateKey and Curve25519.Signing.PublicKey.
Just like that K1 vendors these key pairs:
K1.KeyAgreement.PrivateKey / K1.KeyAgreement.PublicKey for key agreement (ECDH)
K1.Schnorr.PrivateKey / K1.Schnorr.PublicKey for sign / verify methods using Schnorr signature scheme
K1.ECDSAWithKeyRecovery.PrivateKey / K1.ECDSAWithKeyRecovery.PublicKey for sign / verify methods using ECDSA (producing/validating signature where public key is recoverable)
K1.ECDSA.PrivateKey / K1.ECDSA.PublicKey for sign / verify methods using ECDSA (producing/validating signature where public key is not recoverable)
Just like you can convert between e.g. Curve25519.KeyAgreement.PrivateKey and Curve25519.Signing.PrivateKey back and forth using any of the initializers and serializer, you can convert between all PrivateKeys and all PublicKeys of all features in K1.
All keys can be serialized using these computed properties:
{
var rawRepresentation: Data { get }
var derRepresentation: Data { get }
var pemRepresentation: String { get }
var x963Representation: Data { get }
}
All keys can be deserialize using these initializer:
{
init(rawRepresentation: some ContiguousBytes) throws
init(derRepresentation: some RandomAccessCollection<UInt8>) throws
init(pemRepresentation: String) throws
init(x963Representation: some ContiguousBytes) throws
}
Furthermore, all PrivateKey’s have these additional APIs:
{
init()
associatedtype PublicKey
var publicKey: PublicKey { get }
}
Furthermore, all PublicKeys’s have these additional APIs:
{
init(compressedRepresentation: some ContiguousBytes) throws
var compressedRepresentation: Data { get }
}
ECDSA (Elliptic Curve Digital Signature Algorithm)
There exists two set of ECDSA key pairs:
A key pair for signatures from which you can recover the public key, specifically: K1.ECDSAWithKeyRecovery.PrivateKey and K1.ECDSAWithKeyRecovery.PublicKey
A key pair for signatures from which you can not recover the public key, specifically: K1.ECDSA.PrivateKey and K1.ECDSA.PublicKey
For each private key there exists two different signature:for:options (one taking hashed data and taking Digest as argument) methods and one signature:forUnhashed:options.
The option is a K1.ECDSA.SigningOptions struct, which by default specifies RFC6979 deterministic signing, as per Bitcoin standard, however, you can change to use secure random nonce instead.
NonRecoverable
Sign
let alice = K1.ECDSA.PrivateKey()
Hashed (Data)
let hashedMessage: Data = // from somewhere
let signature = try alice.signature(for: hashedMessage)
Digest
let message: Data = // from somewhere
let digest = SHA256.hash(data: message)
let signature = try alice.signature(for: digest)
Hash and Sign
The forUnhashed will SHA256 hash the message and then sign it.
let message: Data = // from somewhere
let signature = try alice.signature(forUnhashed: message)
Validate
Hashed (Data)
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
Digest
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
Hash and Validate
let message: Data = // from somewhere
let signature: K1.ECDSA.Signature // from above
assert(
publicKey.isValidSignature(signature, unhashed: message)
) // PASS
Recoverable
All signing and validation APIs are identical to the NonRecoverable namespace.
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 Signature Scheme
Sign
let alice = K1.Schnorr.PrivateKey()
let signature = try alice.signature(forUnhashed: message)
There exists other sign variants, signature:for:options (hashed data) and signature:for:options (Digest) if you already have a hashed message. All three variants takes a K1.Schnorr.SigningOptions struct where you can pass auxiliaryRandomData to be signed.
Validate
let publicKey: K1.Schnorr.PublicKey = alice.publicKey
assert(publicKey.isValidSignature(signature, unhashed: message)) // PASS
Or alternatively isValidSignature:digest or isValidSignature:hashed.
Schnorr Scheme
The Schnorr signature implementation is BIP340, since we use libsecp256k1 which only provides the BIP340 Schnorr scheme.
Using libsecp256k1 default behaviour, returning a SHA-256 hash of the compressed point, embedded in a CryptoKit.SharedSecret, which is useful since you can use CryptoKit key derivation functions.
Returns an entire uncompressed EC point, without hashing it. Might be useful if you wanna construct your own cryptographic functions, e.g. some custom 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
Acknowledgements
K1 is a Swift wrapper around libsecp256k1, so this library would not exist without the Bitcoin Core developers. Massive thank you for a wonderful library! I’ve included it as a submodule, without any changes to the code, i.e. with copyright headers in files intact.
K1 uses some code from swift-crypto, which has been copied over with relevant copyright header. Since swift-crypto is licensed under Apache, so is this library.
Some of the files in this project are autogenerated (metaprogramming) using the Swift Utils tools called gyb (“generate your boilerplate”). gyb is included in ./scripts/gyb.
gyb will generate some Foobar.swift Swift file from some Foobar.swift.gybtemplate file. You should not edit Foobar.swift directly, since all manual edits in that generated file will be overwritten the next time gyb is run.
More conveniently you can run the bash script ./scripts/generate_boilerplate_files_with_gyb.sh to generate all Swift files from their corresponding gyb template.
If you add a new .gyb file, you should append a // MARK: - Generated file, do NOT edit warning inside it, e.g.
// MARK: - Generated file, do NOT edit
// any edits of this file WILL be overwritten and thus discarded
// see section `gyb` in `README` for details.
K1 🏔
K1 is Swift wrapper around libsecp256k1 (bitcoin-core/secp256k1), offering ECDSA, Schnorr (BIP340) and ECDH features.
Documentation
Read full documentation here on SwiftPackageIndex.
Quick overview
The API of K1 maps almost 1:1 with Apple’s CryptoKit, vendoring a set of keypairs, one per feature. E.g. in CryptoKit you have
Curve25519.KeyAgreement.PrivateKey
andCurve25519.KeyAgreement.PublicKey
which are seperate forCurve25519.Signing.PrivateKey
andCurve25519.Signing.PublicKey
.Just like that K1 vendors these key pairs:
K1.KeyAgreement.PrivateKey
/K1.KeyAgreement.PublicKey
for key agreement (ECDH)K1.Schnorr.PrivateKey
/K1.Schnorr.PublicKey
for sign / verify methods using Schnorr signature schemeK1.ECDSAWithKeyRecovery.PrivateKey
/K1.ECDSAWithKeyRecovery.PublicKey
for sign / verify methods using ECDSA (producing/validating signature where public key is recoverable)K1.ECDSA.PrivateKey
/K1.ECDSA.PublicKey
for sign / verify methods using ECDSA (producing/validating signature where public key is not recoverable)Just like you can convert between e.g.
Curve25519.KeyAgreement.PrivateKey
andCurve25519.Signing.PrivateKey
back and forth using any of the initializers and serializer, you can convert between all PrivateKeys and all PublicKeys of all features in K1.All keys can be serialized using these computed properties:
All keys can be deserialize using these initializer:
Furthermore, all PrivateKey’s have these additional APIs:
Furthermore, all PublicKeys’s have these additional APIs:
ECDSA (Elliptic Curve Digital Signature Algorithm)
There exists two set of ECDSA key pairs:
K1.ECDSAWithKeyRecovery.PrivateKey
andK1.ECDSAWithKeyRecovery.PublicKey
K1.ECDSA.PrivateKey
andK1.ECDSA.PublicKey
For each private key there exists two different
signature:for:options
(one taking hashed data and takingDigest
as argument) methods and onesignature:forUnhashed:options
.The
option
is aK1.ECDSA.SigningOptions
struct, which by default specifiesRFC6979
deterministic signing, as per Bitcoin standard, however, you can change to use secure random nonce instead.NonRecoverable
Sign
Hashed (Data)
Digest
Hash and Sign
The
forUnhashed
willSHA256
hash the message and then sign it.Validate
Hashed (Data)
Digest
Hash and Validate
Recoverable
All signing and validation APIs are identical to the
NonRecoverable
namespace.Schnorr Signature Scheme
Sign
There exists other sign variants,
signature:for:options
(hashed data) andsignature:for:options
(Digest
) if you already have a hashed message. All three variants takes aK1.Schnorr.SigningOptions
struct where you can passauxiliaryRandomData
to be signed.Validate
Or alternatively
isValidSignature:digest
orisValidSignature:hashed
.Schnorr Scheme
The Schnorr signature implementation is BIP340, since we use libsecp256k1 which only provides the BIP340 Schnorr scheme.
It is worth noting that some Schnorr implementations are incompatible with BIP340 and thus this library, e.g. Zilliqa’s (kudelski report, libsecp256k1 proposal, Twitter thread).
ECDH
This library vendors three different EC Diffie-Hellman (ECDH) key exchange functions:
ASN1 x9.63
- No hash, return only theX
coordinate of the point -sharedSecretFromKeyAgreement:with -> SharedSecret
libsecp256k1
- SHA-256 hash the compressed point -ecdh:with -> SharedSecret
ecdhPoint -> Data
ASN1 x9.63
ECDHReturning only the
X
coordinate of the point, following ANSI X9.63 standards, embedded in aCryptoKit.SharedSecret
, which is useful since you can useCryptoKit
key derivation functions on this SharedSecret, e.g.x963DerivedSymmetricKey
orhkdfDerivedSymmetricKey
.You can retrieve the
X
coordinate as raw data usingwithUnsafeBytes
if you need to.libsecp256k1
ECDHUsing
libsecp256k1
default behaviour, returning a SHA-256 hash of the compressed point, embedded in aCryptoKit.SharedSecret
, which is useful since you can useCryptoKit
key derivation functions.Custom ECDH
Returns an entire uncompressed EC point, without hashing it. Might be useful if you wanna construct your own cryptographic functions, e.g. some custom ECIES.
Acknowledgements
K1
is a Swift wrapper around libsecp256k1, so this library would not exist without the Bitcoin Core developers. Massive thank you for a wonderful library! I’ve included it as a submodule, without any changes to the code, i.e. with copyright headers in files intact.K1
uses some code fromswift-crypto
, which has been copied over with relevant copyright header. Sinceswift-crypto
is licensed under Apache, so is this library.Development
Stand in root and run
To clone the dependency libsecp256k1, using commit 427bc3cdcfbc74778070494daab1ae5108c71368 (semver 0.3.0)
gyb
Some of the files in this project are autogenerated (metaprogramming) using the Swift Utils tools called gyb (“generate your boilerplate”).
gyb
is included in./scripts/gyb
.gyb
will generate someFoobar.swift
Swift file from someFoobar.swift.gyb
template file. You should not editFoobar.swift
directly, since all manual edits in that generated file will be overwritten the next timegyb
is run.You run
gyb
for a single file like so:More conveniently you can run the bash script
./scripts/generate_boilerplate_files_with_gyb.sh
to generate all Swift files from their corresponding gyb template.If you add a new
.gyb
file, you should append a// MARK: - Generated file, do NOT edit
warning inside it, e.g.Alternatives
libsecp256k1
, ⚠️ possibly unsafe, ✅ Schnorr support)libsecp256k1
, ❌ No Schnorr)libsecp256k1
, ❌ No Schnorr)