Skip to content

chore: bump github.com/21-dot-dev/swift-secp256k1 from 0.17.0 to 0.24.0 in the swift group across 1 directory - #1185

Open
dependabot[bot] wants to merge 6 commits into
masterfrom
dependabot/swift/swift-89f2110394
Open

dependabot[bot] wants to merge 6 commits into
masterfrom
dependabot/swift/swift-89f2110394

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Bumps the swift group with 1 update in the / directory: github.com/21-dot-dev/swift-secp256k1.

Updates github.com/21-dot-dev/swift-secp256k1 from 0.17.0 to 0.24.0

Release notes

Sourced from github.com/21-dot-dev/swift-secp256k1's releases.

0.24.0

What's Changed

Breaking

  • ZKP products removed: the ZKP and libsecp256k1_zkp products, the zkp bundle trait and its eight module traits, and the SharedSourcesPlugin build tool are gone. The package ships P256K + libsecp256k1 with zero build plugins — Xcode, headless CI, and Xcode Cloud now build with no trust prompt and no -skipPackagePluginValidation.
    • Vanilla-surface consumers: switch .product(name: "ZKP")/import ZKP to P256K — identical API.
    • Consumers of the zkp-only C modules: pin exact: "0.23.2" with traits: ["zkp"] — 0.23.x receives no security fixes, so pin only until a dedicated swift-secp256k1-zkp package ships — see the MigratingToP256K article.
  • MuSig nonce API hardened: P256K.MuSig.Nonce.generate(sessionID:secretKey:publicKey:msg32:extraInput32:) replaced by generate(sessionSecretRand:secretKey:publicKey:msg32:extraInput32:) taking the new ~Copyable P256K.MuSig.SessionSecretRand type — consumed by the call so session randomness cannot be reused, storing its bytes in auto-zeroing SecureBytes. msg32 is now [UInt8]? (nil = message not yet known, matching upstream's NULL msg32) in both generate overloads.
  • XCFramework toolchain: the shipped P256K.xcframework is now built on Xcode 27 (previously a runner-default Xcode via a generated project) and verified end-to-end before shipping — per-slice export allowlists plus real consumer builds on every shipped platform — instead of post-editing module interfaces. Emitted interfaces carry Xcode 27's P256K::P256K module-selector spelling: binary consumers (CocoaPods) need a Swift ≥6.3 toolchain to read them. Source and SwiftPM builds are unaffected.

Security fixes (MuSig)

  • P256K.MuSig.Nonce.generate no longer reads (and, for sessionID, writes) past the end of short input arrays — sessionSecretRand is a 32-byte type invariant, and msg32/extraInput32 must be exactly 32 bytes when present or secp256k1Error.incorrectParameterSize is thrown before reaching libsecp256k1.
  • MuSig partialSignature(for:…) and both MuSig tweak methods now throw secp256k1Error.incorrectParameterSize on non-32-byte digest/tweak inputs instead of an out-of-bounds C read.
  • secretKey: nil no longer crashes on a force unwrap in P256K.MuSig.Nonce.generate — nil reaches upstream as NULL as documented, and a non-nil key is read from its SecureBytes backing rather than a temporary copy.
  • Internally generated session randomness is now the correct 32 bytes (was 133 — the extra bytes were never read upstream).

Also

  • New MigratingToP256K DocC guide covering the import secp256k1 → import P256K rename across the GigaBitcoin lineage — and how to tell when the move is a port, not a rename.
  • Vendoring migrated from git-subtree to vendir — upstream sources sync directly into Sources//Projects/ per vendir.yml + vendir.lock.yml; libsecp256k1Tests compiles the shipped Sources/libsecp256k1 tree. The Vendor/ mirror, subtree.yaml, subtree workflows, and the swift-plugin-subtree dev dependency are removed.
  • swift-crypto's LICENSE.txt/NOTICE.txt now ship in the XCFramework zip.
  • Projects/README.md's xcodebuild test examples corrected — P256KTests runs inside the P256K scheme (there is no P256KTests scheme); BindingsTests joins the run-all list.

Full Changelog: https://github.com/21-DOT-DEV/swift-secp256k1/blob/main/CHANGELOG.md

0.23.2

Highlights

Other Changes

Dev-tooling Bumps

Full Changelog: 21-DOT-DEV/swift-secp256k1@0.23.1...0.23.2

0.23.1

... (truncated)

Changelog

Sourced from github.com/21-dot-dev/swift-secp256k1's changelog.

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Migrating from 0.24.x

  • Breaking: P256K.Recovery.PublicKey.init(_:signature:format:) (both Data and Digest overloads) now throws — secp256k1Error.underlyingCryptoError when secp256k1_ecdsa_recover rejects the signature — where a failed recovery previously reached fatalError and terminated the process
  • Breaking: P256K.Signing.XonlyKey.init(dataRepresentation:keyParity:), P256K.Schnorr.XonlyKey.init(dataRepresentation:keyParity:cache:), and P256K.MuSig.XonlyKey.init(dataRepresentation:keyParity:cache:) now throws — the coordinate is validated with secp256k1_xonly_pubkey_parse at construction (length and curve-point membership), so a malformed x-only key is no longer representable: wrong byte counts throw secp256k1Error.incorrectKeySize and an X coordinate with no curve point throws secp256k1Error.underlyingCryptoError. Downstream APIs lean on the invariant instead — init(xonlyKey:) on all three public-key types remains non-throwing
  • Breaking: P256K.Signing.ECDSASignature.dataRepresentation now stores the canonical 64-byte r || s big-endian wire form — identical to compactRepresentation — instead of the platform-dependent opaque layout of the secp256k1_ecdsa_signature C struct. init(dataRepresentation:) validates through secp256k1_ecdsa_signature_parse_compact before storing: a wrong byte count throws secp256k1Error.incorrectParameterSize; r/s scalars at or above the curve order throw secp256k1Error.underlyingCryptoError. Do not reload dataRepresentation bytes persisted by ≤0.24.0: they are a different format — old bytes may still parse but decode as a different (r, s) pair, so verification would fail or the initializer would throw. The migration path is compactRepresentation (already canonical under ≤0.24.0) re-supplied through init(dataRepresentation:)
  • Breaking: P256K.Recovery.ECDSASignature.dataRepresentation is now private(set) (it can no longer be mutated after initialization) and stores the canonical 65-byte r || s || recoveryId wire form — big-endian r || s plus one recovery-ID byte, i.e. what secp256k1_ecdsa_recoverable_signature_serialize_compact produces — instead of the platform-dependent opaque layout of the secp256k1_ecdsa_recoverable_signature C struct (a limb-order memory dump on 64-bit platforms). Both initializers (init(dataRepresentation:) and init(compactRepresentation:recoveryId:)) now validate through secp256k1_ecdsa_recoverable_signature_parse_compact before storing: wrong byte counts throw secp256k1Error.incorrectParameterSize; a recovery ID outside 0...3 or r/s scalars at or above the curve order throw secp256k1Error.underlyingCryptoError. Do not reload dataRepresentation bytes persisted by ≤0.24.0: they are a different format — old bytes may still parse but decode as a different (r, s) pair, so recovery would return the wrong public key or throw. The migration path is compactRepresentation (already canonical under ≤0.24.0) re-supplied through init(compactRepresentation:recoveryId:)
  • Breaking: P256K.Schnorr.PartialSignature.dataRepresentation is now private(set) and stores the canonical 32-byte wire form produced by secp256k1_musig_partial_sig_serialize — previously it stored the 36-byte opaque secp256k1_musig_partial_sig struct (32 wire bytes followed by 4 unused bytes). init(dataRepresentation:session:) validates both inputs before storing: the signature must parse via secp256k1_musig_partial_sig_parse and the session must carry the exact 133-byte size and upstream magic prefix; violations throw secp256k1Error.incorrectParameterSize or secp256k1Error.underlyingCryptoError. Do not reload dataRepresentation bytes persisted by ≤0.24.0 — they include the trailing padding and are rejected
  • Breaking: HashDigest.init(_:) — also reachable via the SHA256Digest typealias — now throws: anything other than exactly 32 bytes throws secp256k1Error.incorrectParameterSize, where previously fewer than 32 bytes trapped on out-of-range slicing and more than 32 silently kept only the first 32. SHA256.hash/taggedHash produce their digest through an internal validated initializer instead
  • Breaking: HashDigest.byteCount no longer has a public setter — the setter existed only to fatalError, so the property is now get-only, matching the Digest protocol's requirement
  • Breaking: The ContiguousBytes.bytes convenience accessor is renamed byteArray and gated on the new public CopyableBytes marker protocol. The rename removes a collision with Foundation's Data.bytes (RawSpan) on newer SDKs, where Data's own member shadows any extension; the gate keeps the accessor from materializing a never-zeroed [UInt8] copy of secret storage (SecureBytes, which backs SharedSecret and the MuSig SecureNonce, is internal and deliberately non-conforming). Reading .byteArray on a non-conforming type fails to compile with "requires that … conform to 'CopyableBytes'"; .bytes remains as a @available(deprecated, renamed: "byteArray") alias on conforming types. Conformances ship for Data, Array, ArraySlice, SharedSecret, HashDigest, P256K.Signing.ECDSASignature, P256K.Recovery.ECDSASignature, P256K.Schnorr.SchnorrSignature, P256K.Schnorr.Nonce, P256K.MuSig.Nonce, P256K.MuSig.AggregateSignature, and P256K.Schnorr.PartialSignature — every public non-secret ContiguousBytes type in the package. Other conformers — consumer-defined types, String.UTF8View, DispatchData, CryptoKit types — lose the member; the migration is withUnsafeBytes { Array($0) } at the call site, or extension MyType: CopyableBytes to restore it
  • Breaking: P256K.Schnorr.PrivateKey.signature(message:auxiliaryRand:strict:) is replaced by named-intent methods — signature(message:) and signature(message:auxiliaryRand:) require an exactly-32-byte message and throw secp256k1Error.incorrectParameterSize otherwise; signature(customMessage:) and signature(customMessage:auxiliaryRand:) sign arbitrary-length messages (upstream's secp256k1_schnorrsig_sign_custom). The inout message array, the UnsafeMutableRawPointer? aux pointer, and the strict flag are gone; nil aux — silent deterministic nonce derivation — is unexpressible, and a zeroed 32-byte container reproduces the all-zero-aux semantics explicitly. signature(for:auxiliaryRand:) is retyped from [UInt8] to some ContiguousBytes — existing [UInt8] call sites compile unchanged, and the aux bytes are now borrowed for the C call instead of copied into un-zeroed heap storage
  • Breaking: P256K.Schnorr.XonlyKey.isValid(_:for:) is retyped from inout [UInt8] to some ContiguousBytes — the method never mutated the buffer, so the inout requirement is gone. Call sites passing &array drop the & and compile unchanged, as do [UInt8] and Data arguments; ArraySlice and other ContiguousBytes conformers are now accepted. isValidSignature(_:for:) keeps its Digest signature — it verifies the digest's bytes at natural length through the same method and no longer copies them into a mutable array first
  • Breaking: P256K.MuSig.XonlyKey.isValid(_:for:) is retyped from inout [UInt8] to some ContiguousBytes, mirroring the Schnorr verifier — the method never mutated the buffer, so the inout requirement is gone. Call sites passing &array drop the & and compile unchanged, as do [UInt8] and Data arguments; ArraySlice and other ContiguousBytes conformers are now accepted. isValidSignature(_:for:) keeps its Digest signature — it verifies the digest's bytes at natural length through the same method (the msg32 reduction is dropped; see the message-length entries below) and no longer copies them into a mutable array first
  • Breaking: P256K.Schnorr.PrivateKey.partialSignature(for:…) and both P256K.MuSig.Nonce.generate(…msg32:…) overloads now require an exactly-32-byte message — secp256k1Error.incorrectParameterSize otherwise — where a non-32-byte input was previously SEC1-normalized (left-padded or truncated to its leftmost 32 bytes) and silently committed the signing session to the wrong message. Both parameters are retyped: partialSignature's for: input is now some ContiguousBytes (parameter message, previously D: Digest) and msg32: is now (any ContiguousBytes)? — [UInt8] and Digest arguments compile unchanged, and nil still means "message not yet known" (the existential spelling is required — (some ContiguousBytes)? cannot accept a nil literal). Hash arbitrary-length application messages to 32 bytes first — variable-length BIP-327 messages are unsupported by libsecp256k1's fixed-msg32 API. The consumed secureNonce is destroyed on every partialSignature exit including the new throw — a rejected message requires regenerating the nonce pair; likewise SessionSecretRand on Nonce.generate, so a rejected msg32/extraInput32 requires fresh session randomness
  • Breaking: P256K.MuSig.XonlyKey.isValidSignature(_:for:) no longer reduces the digest to a 32-byte msg32 — it verifies the digest's bytes at their natural length through secp256k1_schnorrsig_verify's arbitrary-length message API, matching P256K.Schnorr.XonlyKey.isValidSignature(_:for:). A signature that previously verified against a non-32-byte digest's truncated form now fails closed — pass the exact 32-byte message the signing session bound
  • Breaking: P256K.MuSig.aggregate(_:) no longer sorts the input keys internally — it now preserves the caller's key order, matching BIP-327's ordered KeyAgg where the second distinct key takes coefficient 1, so different orderings of the same key multiset generally produce different aggregates. Consumers relying on the previous order-independent behavior must sort explicitly first: P256K.MuSig.aggregate(P256K.MuSig.sortedPublicKeys(pubkeys)) reproduces the ≤0.24.0 result exactly
  • Breaking: P256K.MuSig.PublicKey.isValidSignature(_:publicKey:nonce:for:) is replaced by isValidSignature(_:publicKey:nonce:for:publicNonceAggregate:) throws -> Bool — the for: parameter (previously D: Digest, documented "not consulted") is retyped to some ContiguousBytes and is now consulted: the method rebuilds the signing session via secp256k1_musig_nonce_process from the new required publicNonceAggregate: (the P256K.MuSig.Nonce already held from the signing round), the 32-byte message, and the aggregate key's aggregation cache — upstream's documented verifier flow — then requires the session blob carried inside the PartialSignature to match it before calling secp256k1_musig_partial_sig_verify. A partial signature produced over a different message or nonce set — or paired with a foreign session blob through init(dataRepresentation:session:) — now returns false where it previously verified against the session it carried. The new throws reports the one malformed-input case truthfully: a non-32-byte for: input throws secp256k1Error.incorrectParameterSize like the package's other fixed-msg32 inputs (write (try? …) ?? false to recover Bool-only semantics)
  • Breaking: P256K.MuSig.aggregateSignatures(_:) is replaced by aggregateSignatures(_:for:publicNonceAggregate:xonlyKeyAggregate:), with a publicKeyAggregate: convenience overload mirroring partialSignature's pair — the caller re-declares the signing session's three inputs (the 32-byte message, the aggregated public nonce, and the key aggregate carrying its aggregation cache), the method rebuilds the session via secp256k1_musig_nonce_process, and every partial signature's carried session blob must match it byte-for-byte or secp256k1Error.underlyingCryptoError throws. Signatures produced over a different message, nonce set, or key aggregate — previously aggregated silently under the first signature's session — are now rejected; and the produced 64-byte aggregate is verified via secp256k1_schnorrsig_verify before returning (see Changed), so a partial_sig_agg failure or a partial_sig_parse failure on construction-validated bytes is unreachable and reports through P256K.invariantFailure instead of throwing
  • Breaking: P256K.Schnorr.PrivateKey.partialSignature(for:pubnonce:…) now validates the pubnonce: parameter — previously documented "not consulted", it is byte-compared against the companion public nonce SecureNonce retains from its Nonce.generate pair and throws secp256k1Error.underlyingCryptoError on mismatch before the nonce reaches upstream, so a caller's nonce-bookkeeping error surfaces at the signer where it is provable instead of as an unattributed session failure downstream

Added

  • P256K.setInvariantViolationHandler(_:) — installs (or clears, with nil) an optional (@Sendable (String) -> Void) reporter invoked when the library detects a state the libsecp256k1 API contract makes unreachable (e.g. secp256k1_ecdsa_sign failing on an already-validated key, which can only indicate memory corruption or a defect in this library). The handler is for observability only — logging, metrics, crash artifacts — and the process still terminates via fatalError after it returns, following libsodium's sodium_set_misuse_handler model where proven-impossible states fail-stop rather than continue with undefined cryptographic state. Installs are serialized under an internal lock and may happen at any time; the handler is invoked at most once per process, and a concurrent violation on another thread waits for the first report to complete before the process terminates (the dispatching thread holds the lock through the handler and fatalError, matching libsodium's sodium_crit behavior). Contexts created by P256K.Context.create() also route libsecp256k1's own illegal-argument (ARG_CHECK) and internal-error callbacks through the same reporter, so those are observed identically rather than aborting anonymously to stderr
  • P256K.Schnorr.PrivateKey.signature(message:) and signature(message:auxiliaryRand:) — Schnorr signing over an exactly-32-byte message (throws secp256k1Error.incorrectParameterSize otherwise), with fresh or caller-supplied auxiliary randomness — plus signature(customMessage:) and signature(customMessage:auxiliaryRand:) for arbitrary-length messages, completing the {digest, 32-byte, arbitrary} × {fresh, caller-supplied} randomness matrix so no signing intent requires caller-generated randomness
  • P256K.MuSig.sortedPublicKeys(_:) — sorts signer public keys into the canonical lexicographic order secp256k1_ec_pubkey_sort produces (the same ordering BIP-390 musig() descriptors use), so callers can opt into order-independent aggregation: aggregate(sortedPublicKeys(keys)) yields the same aggregate for every assembly order of the same key multiset. Each returned key keeps its stored serialization format — sorting compares the parsed points, not the wire forms

Changed

  • Digest parameters feeding fixed 32-byte upstream msg32 reads are now normalized per SEC1 §4.1.3 instead of rejected or read out of bounds — a digest longer than 32 bytes contributes its leftmost 32 bytes and a shorter digest contributes its integer value (left-padded with zeros). Applies to P256K.Signing.PrivateKey.signature(for:) and isValidSignature(_:for:), P256K.Recovery.PrivateKey.signature(for:) and Recovery.PublicKey.init(_:signature:format:) — matching how Go crypto/ecdsa, OpenSSL, and FIPS 186-5 treat the message representative. isValidSignature(_:for:) verifies the normalized digest honestly rather than failing closed. MuSig's message inputs are the exception — they no longer normalize at all: partialSignature(for:…) and Nonce.generate(…msg32:) require exactly 32 bytes, and MuSig.XonlyKey.isValidSignature(_:for:) checks the digest at natural length (see Breaking). Fixed-size non-digest inputs — ECDSA/Schnorr/MuSig tweaks, Schnorr auxiliaryRand, MuSig extraInput32, SessionSecretRand — still throw secp256k1Error.incorrectParameterSize on the wrong byte count
  • Every trap guarding a state the libsecp256k1 API contract makes unreachable — upstream serialize/parse/key-generation calls on already-validated values — now routes through P256K.invariantFailure, which notifies the handler installed via P256K.setInvariantViolationHandler(_:) before terminating, instead of bare fatalError/precondition. Unreachable-class calls that shared a guard with reachable ones inside throwing methods — keypair creation and accessors in Schnorr/MuSig tweaks, MuSig key aggregation and nonce generation, Schnorr and MuSig signing, key combination and derivation — are now split into per-call checks, so only genuinely reachable failures (invalid tweaks, point-at-infinity results, malformed caller-supplied bytes) still throw secp256k1Error.underlyingCryptoError
  • P256K.Schnorr.PrivateKey signing methods now verify each produced signature against the key's own xonly identity via secp256k1_schnorrsig_verify before returning it — BIP-340 footnote 14's verify-after-sign check, which upstream's secp256k1_schnorrsig.h explicitly delegates to callers and which Bitcoin Core performs in KeyPair::SignSchnorr. A verification failure is unreachable after input validation — it can only indicate output corruption or a library defect — so it reports through P256K.invariantFailure and terminates rather than letting a corrupt signature escape. Signing now costs roughly one signature verification more than before; the produced 64-byte signature is unchanged
  • P256K.Signing.PrivateKey ECDSA signing methods now verify each produced signature against the key's own publicKey via secp256k1_ecdsa_verify before returning it — the verify-after-sign check Bitcoin Core applies in CKey::Sign (PR #22934). A verification failure is unreachable after input validation — it can only indicate output corruption or a library defect — so it reports through P256K.invariantFailure and terminates rather than letting a corrupt signature escape. Signing now costs roughly one signature verification more than before; the produced 64-byte signature is unchanged
  • P256K.Recovery.PrivateKey signing methods now verify each produced recoverable signature before returning it — the serialized 65-byte wire form is re-parsed, its signer key recovered via secp256k1_ecdsa_recover, and compared to the key's own publicKey with secp256k1_ec_pubkey_cmp — the same check Bitcoin Core applies in CKey::SignCompact. Re-parsing the wire form re-validates the recovery ID at the boundary upstream reserves for it — inside the opaque signature struct the ID is only masked recid & 3 in release builds, so a corrupt ID could otherwise alias a valid one and recover the same key — and a successful recovery itself guarantees the (r, s) pair verifies for the recovered key. A failure is unreachable after input validation — it can only indicate output corruption or a library defect — so it reports through P256K.invariantFailure and terminates rather than letting a corrupt signature escape. Signing now costs roughly one key recovery more than before; the produced 65-byte signature is unchanged
  • P256K.Schnorr.PrivateKey.partialSignature(for:…) now verifies each produced partial signature before returning it — the serialized 32-byte wire form is re-parsed and verified via secp256k1_musig_partial_sig_verify under the session rebuilt in the same call, the companion public nonce stored inside the consumed SecureNonce, and the signing key's public half — the check BIP-327's reference Sign ends with (assert partial_sig_verify_internal) and upstream secp256k1_musig.h explicitly delegates to callers. A verification failure is unreachable after input validation — it can only indicate output corruption or a library defect — so it reports through P256K.invariantFailure and terminates rather than letting a corrupt signature escape. Signing now costs roughly one partial-signature verification more than before; the produced 32-byte partial signature is unchanged
  • P256K.MuSig.aggregateSignatures now verifies the produced 64-byte aggregate signature via secp256k1_schnorrsig_verify against the aggregate x-only key derived from the declared key aggregation cache — the final check upstream's doc/musig.md prescribes for the aggregator, since secp256k1_musig_partial_sig_agg returning 1 does not guarantee a valid aggregate (a tweaked key aggregation is handled automatically — secp256k1_musig_pubkey_get yields the tweaked point). A verification failure is unreachable after input validation — it can only indicate output corruption or a library defect — so it reports through P256K.invariantFailure and terminates rather than letting a corrupt signature escape. Aggregation now costs roughly one session rebuild plus one signature verification more than before; the produced 64-byte aggregate signature is unchanged
  • P256K.Schnorr.SecureNonce retains the companion public nonce emitted alongside it by Nonce.generate — upstream's opaque secp256k1_musig_secnonce binds the nonce pair's scalars but exposes no public-nonce accessor, so the emitted value is stored rather than recomputed — which partialSignature uses to validate the declared pubnonce: and to self-verify the produced partial signature

Fixed

... (truncated)

Commits
  • bcd6dd8 docs: prep the 0.24.0 release and close out spec 003 (#1214)
  • 38071bf docs: re-home zkp-tier roadmap work and log the ZKP removal (#1213)
  • 46067a9 docs: sweep zkp-trait and plugin-trust prose from the DocC guides (#1212)
  • 15ee5dc docs: rewrite Sources/AGENTS.md for the single-target layout (#1211)
  • 8d83197 docs: sync the root docs to the post-ZKP-removal layout (#1210)
  • 0be8788 ci: drop the ZKP lanes and plugin-validation skips (#1209)
  • bb4f53b fix: harden the MuSig nonce-generation boundary against short buffers and non...
  • 5ef79d6 refactor: collapse the dead libsecp256k1_zkp conditionals to plain imports (#...
  • c0032a1 remove the ZKP products and fold shared sources into P256K (#1206)
  • 74cd2fe drop the libsecp256k1_zkp vendir entry ahead of the teardown (#1205)
  • Additional commits viewable in compare view

@dependabot dependabot Bot added dependencies Dependencies related swift_package_manager Pull requests that update swift_package_manager code labels Nov 1, 2026
@greptile-apps

greptile-apps Bot commented Nov 1, 2026

Copy link
Copy Markdown

This PR does not match any of the 1 configured review trigger rule.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependency bump of a crypto SDK. It does not build: one HIGH inline. CI is red at 37e2079 in Run Tests, Run Integration Tests and build-local for the reason the bump causes. No device check until it builds.

Comment thread Bitkit.xcodeproj/project.pbxproj Resolved
@jvsena42

jvsena42 commented Nov 5, 2026

Copy link
Copy Markdown
Member

@dependabot rebase

Bumps the swift group with 1 update in the / directory: [github.com/21-dot-dev/swift-secp256k1](https://github.com/21-DOT-DEV/swift-secp256k1).


Updates `github.com/21-dot-dev/swift-secp256k1` from 0.17.0 to 0.24.0
- [Release notes](https://github.com/21-DOT-DEV/swift-secp256k1/releases)
- [Changelog](https://github.com/21-DOT-DEV/swift-secp256k1/blob/main/CHANGELOG.md)
- [Commits](21-DOT-DEV/swift-secp256k1@0.17.0...bcd6dd8)

---
updated-dependencies:
- dependency-name: github.com/21-dot-dev/swift-secp256k1
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swift
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot changed the title chore: bump github.com/21-dot-dev/swift-secp256k1 from 0.17.0 to 0.24.0 in the swift group chore: bump github.com/21-dot-dev/swift-secp256k1 from 0.17.0 to 0.24.0 in the swift group across 1 directory Nov 5, 2026
@dependabot
dependabot Bot force-pushed the dependabot/swift/swift-89f2110394 branch from 37e2079 to fc2da0d Compare November 5, 2026 19:04
@jvsena42 jvsena42 self-assigned this Nov 5, 2026
@jvsena42
jvsena42 marked this pull request as draft November 5, 2026 19:07
….24.0

0.24.0 renames the secp256k1 product/module to P256K and stops
re-exporting the C API, which now lives in the libsecp256k1 product.
sharedSecretFromKeyAgreement no longer throws.

Add fixed-vector tests for Crypto.sign and Crypto.getPublicKey.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jvsena42

jvsena42 commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

swift-secp256k1 0.17.0 → 0.24.0: security review and fix

Upstream tags: 0.17.0 = 9683e8e3, 0.24.0 = bcd6dd8e (these match Package.resolved).

Build fix (020a50f)

The version bump alone does not compile. In 0.24.0 the secp256k1 product/module is renamed to P256K, and the C API is no longer re-exported (InternalImportsByDefault). It now ships as a separate libsecp256k1 product.

  • The Bitkit, BitkitNotification and BitkitTests targets now link P256K + libsecp256k1
  • import P256K / import libsecp256k1, and secp256k1.KeyAgreement → P256K.KeyAgreement
  • Removed try from sharedSecretFromKeyAgreement, which no longer throws (the BitkitNotification build treats this warning as an error)

Findings

No vulnerabilities introduced. Severity of each item is in brackets.

Vendored C / supply chain

  • [none] 0.24.0 vendors bitcoin-core/secp256k1 v0.7.1 (1a53f496). 98 of 100 files are byte-identical to upstream. The other two are the declared shims include/Utility.h and src/Utility.c, unchanged from 0.17.0 apart from a comment. 0.17.0 vendored upstream 4155e62f (v0.5.0+8) through symlinks, also unmodified. The vendored swift-crypto is byte-identical to apple/swift-crypto 4.5.0.
  • [none, improvement] Between the two upstream versions, memset on secrets is replaced by secp256k1_memclear_explicit. This covers ECDH, the RFC 6979 nonce state, ecdsa_sign and ec_pubkey_create, all on our paths. There are no ECDH or recovery behaviour changes, and no CVEs or advisories in this range for either repo.
  • [none, improvement] Package.swift has no binaryTarget, no build plugins and no build-time scripts. The SharedSourcesPlugin and the ZKP products are removed. Dev dependencies are excluded when resolving a tag. Keep the exactVersion pin: pinning by branch or revision would pull in dev command plugins (they are still not attached to the build).
  • [low, pre-existing] secp256k1_swift_sha256 in the shim does not clear the hasher state after finalize. We do not reach it.

Swift wrapper (APIs Bitkit uses)

  • [none] KeyAgreement.PrivateKey/PublicKey(dataRepresentation:) accepts and rejects the same inputs as before (length, prefix, off-curve, zero/overflow scalar), so isValidCompressedPublicKey is unchanged. sharedSecretFromKeyAgreement(format: .compressed) still returns the raw 33-byte compressed point through the same hash closure. Derived AES-GCM keys are identical.
  • [low, fixed in b1792d8] ECDH failure now hits fatalError instead of throwing. It is unreachable with keys the wrapper has already validated. Crypto.generateSharedSecret now calls secp256k1_ecdh directly with a compressed-point hash, so a failure throws. The output is unchanged (Blocktank vector), and the local secret buffers are zeroized.
  • [none] The context setup, randomization and RNG source are unchanged. The 0.24.0 security fixes (MuSig nonce out-of-bounds reads, force unwrap, session randomness size) are all in MuSig code we do not use.
  • Raw C calls (context_create(SECP256K1_CONTEXT_SIGN), ec_seckey_verify, ec_pubkey_create/serialize, ecdsa_sign_recoverable, recoverable_signature_serialize_compact) have the same signatures. SECP256K1_CONTEXT_SIGN is still defined and was already deprecated as equivalent to NONE. The recovery and ECDH modules are compiled in Xcode builds.

Pre-existing, not changed by this bump

  • [low, fixed in b1792d8] Crypto.sign and Crypto.getPublicKey pass privateKey to C without checking that it is 32 bytes. A shorter Data would over-read. The only callers pass a derived 32-byte node secret. All three functions now throw invalidPrivateKey for keys that are not 32 bytes.

Testing

  • Regression tests (b1792d8) cover wrong-length private keys (0/1/31/33/64 bytes), zero and curve-order private keys, and invalid public keys (wrong length, 65-byte uncompressed, bad prefix, off-curve x, x ≥ p). CryptoTests + WatchOnlyAccountServiceTests: 48/48 pass on the simulator. The direct ECDH code was reviewed separately for memory safety and matching output.
  • CryptoTests (existing Blocktank ECDH/AES-GCM vector, plus new fixed vectors for sign and getPublicKey computed with an independent implementation) and WatchOnlyAccountServiceTests: 41/41 pass on an iPhone 17 simulator (iOS 26.5).
  • Live: RNBackupClient.setup + hasBackup() against bitkit.stag0.blocktank.to/backups-ldk succeeded. The server verified both signed auth steps (timestamp and challenge) produced by 0.24.0.
  • Not runnable here: APNs push decryption. The ECDH path it uses has no non-test caller, and the Blocktank ECDH vector covers it.

🤖 Generated with Claude Code

@jvsena42
jvsena42 marked this pull request as ready for review November 5, 2026 19:18
generateSharedSecret now calls secp256k1_ecdh directly with a
compressed-point hash, so a failure throws instead of hitting the
P256K wrapper's fatalError. Output is unchanged (Blocktank vector).
Local shared-secret buffers are zeroized.

sign and getPublicKey reject private keys that are not 32 bytes;
shorter Data was read past its end by the C calls.

Add regression tests for key length, out-of-range keys and invalid
public keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jvsena42 and others added 3 commits November 5, 2026 18:07
swift-secp256k1 0.22.0+ gates its ECDH, recovery, Schnorr and MuSig C
modules behind SwiftPM package traits, which Xcode supports from 26.4.
On Xcode 16.4 and 26.2 those modules are compiled out and the app fails
to link. Move every Xcode workflow to Xcode 26.6 on a pinned macos-26
runner (macos-15 tops out at 26.3) with an explicit iOS 26.5 iPhone 17
simulator, replacing macos-latest, OS=latest and newest-runtime lookups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ubuntu-latest currently resolves to 24.04; pin it so image upgrades
happen deliberately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependencies related swift_package_manager Pull requests that update swift_package_manager code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant