Skip to content

build: DKLs23 0.6.1 and FROST 3.0.0; DKLs23 refresh, key import and BOLT 3 - #12

Merged
canesin merged 13 commits into
mainfrom
chore/dkls23-0.6
Oct 21, 2026
Merged

canesin merged 13 commits into
mainfrom
chore/dkls23-0.6

Conversation

@canesin

@canesin canesin commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Moves libtss to the DKLs23 0.6.1 and FROST 3.0.0 releases. Folds in the open PRs and issue #10.

Item Change
Dependencies dkls23-secp256k1/dkls23-secp256r1 0.6.1 and FROST 3.0.0 from crates.io instead of the git main branches; k256/p256 stable 0.14.
#11 Fixes the same build break (FROST main moved to rand_core 0.10) by pinning the FROST 3.0.0 release instead of tracking main. libtss-wasm (no lockfile) builds again from a fresh resolution.
#7 Chain code accessor (chain_code(), tss_handle_chain_code, every binding), cherry-picked with authorship; reads DKLs23 Party::chain_code().
#8 (refresh part) DKLs23 complete refresh for secp256k1 and secp256r1: RefreshSession::new_dkls, tss_refresh_new_dkls, Go/Node/Python/.NET. Written once, generic over the curve; the public key package is updated with DKLs23 PublicKeyPackage::refresh, which verifies every proof commitment. Share repair itself moves to DKLs23, as discussed on #8 (released later in DKLs23 0.6.2).
#10 DKLs23 key import (trusted dealer) through re_key: dkls_split_key, tss_dkls_split_key, Go/Node/Python/.NET. Keeps the key's public key and, optionally, its BIP-32 chain code. Documented follow-up: a complete refresh.
#9 (reworked) BOLT 3 derivations instead of raw tweaks: bolt3_derive, bolt3_derive_revocation and their public-key-package variants, tss_bolt3_*, Go/Node/Python/.NET. Checked against the BOLT 3 Appendix E vectors.

Fixes from review (codex, agy) and from the new tests:

  • Abort culprits: DKLs23 culprits named the reporting party instead of the accused one (DKG, signing, refresh), so an application following the spec ("exclude the culprit and retry") would exclude an honest party. Culprits now come from the AbortReason; local-state errors and session mismatches accuse no one.
  • Import validation: importing a DKLs23 share checks its threshold parameters against the public key package.
  • C API: handle getters clear the out buffer on error, and split functions cannot leak handles on a late failure.
  • Secret wiping: refresh fragments, the import secret and the per-commitment secret are wiped on every path.
  • Bindings: they validate DKLs23 thresholds before narrowing, and document the depth-1 limit of Taproot chain codes.
  • .NET: *_next marshalled out_complete as bool*, which .NET rejects, so no DKG, sign or refresh session could advance. It is now a byte.
  • Node: koffi registers struct names process-wide; loading the bindings from a second test file failed.
  • Go, Node and React Native gained the Secp256r1ECDSA ciphersuite.
  • .NET: a stale memory-hardening test no longer compiled.

Not folded:

Compatibility

  • Peers: all parties must run DKLs23 0.6.x; its messages are not compatible with earlier versions.
  • Stored key shares: shares exported by current main import and sign. Tested by exporting 2-of-3 secp256k1/secp256r1 shares from main and signing with them here.
  • Recommended: run a complete refresh (RefreshSession::new_dkls) after upgrading, so OT state created before DKLs23 0.5.2 is regenerated.

Test plan

  • cargo fmt --check, cargo clippy --workspace --all-targets -D warnings
  • cargo test --workspace (release): 222 tests, including frozen pre-upgrade key-share fixtures
  • Go go test ./... and go vet
  • Node jest: 40 tests
  • Python pytest: 49 tests
  • .NET dotnet test: 49 tests
  • WASM wasm-pack test --node --features testing
  • React Native jest: 25 tests
  • Cross-version key share import and refresh (libtss/tests/fixtures/dkls23-0.4)
  • External reviews (codex, agy), two rounds of fixes

Follow-ups

  • DKLs23: re_key and refresh_complete_phase1 did not zeroize their temporary polynomials (fixed in DKLs23 0.6.2; libtss moves to it in a follow-up PR).
  • WASM: does not expose the DKLs23 refresh, import or BOLT 3 yet.
  • Pre-existing, not changed here: FROST split in Node/Python narrows out-of-range thresholds; the key-share export format in spec/08-serialization.md is out of date.

canesin and others added 12 commits October 21, 2026 09:43
Depend on dkls23-secp256k1 and dkls23-secp256r1 0.6.1 instead of the
DKLs23 git main branch, and move k256 and p256 from release candidates
to 0.14. Update the spec's DKLs23 version and encoding references.
Depend on the FROST 3.0.0 release instead of the FROST git main branch,
which moved to rand_core 0.10 and no longer accepts OsRng. A fresh
dependency resolution (as in libtss-wasm, which has no lockfile) now
builds again. Reported in #11.
CI runs clippy on the latest stable with -D warnings, and that version
rejects uninlined format args, so every pull request fails before its own
changes are even checked.

Inlines the four format! calls it flags, in the tokio example, the FFI
memory hardening test and the protocol tests.
A wallet service hands out a new address for every customer or payment,
and today each one needs every party to run derive_child. The group key
plus the chain code make a BIP-32 xpub, so a server can derive addresses
on its own, and they match the ones the parties later sign for.

Adds chain_code() to the key share handle, in Rust, the C API
(tss_handle_chain_code) and every binding. DKLs23 returns the chain code
made at DKG; FROST Taproot returns the value derive.rs already uses,
through one shared helper. Other suites return ProtocolMismatch. Tests
check child keys against a separate BIP-32 implementation, and the FFI
tests get a shared DKG and signing driver.
GenerateDealer returns (Handles, PublicKeyPackage) and takes no out
parameter, so the memory-hardening tests no longer compiled.
RefreshSession::new_dkls runs a DKLs23 complete refresh across all n
parties: every share moves by a share of zero, the group key stays the
same and the pairwise OT and zero-share state is regenerated. The refresh
is written once, generic over the curve, and updates the public key
package with DKLs23's PublicKeyPackage::refresh, which verifies every
proof commitment. A failed round ends the session.

Adds tss_refresh_new_dkls to the C API, NewRefreshSessionDKLs and the
Secp256r1ECDSA ciphersuite to the Go binding, tests in Rust, the FFI and
Go, and updates the spec.

Based on the refresh in #8.

Co-authored-by: José A.P <53834183+Jossec101@users.noreply.github.com>
dkls_import::dkls_split_key splits a secp256k1 or secp256r1 secret key
into DKLs23 shares through DKLs23 re_key (trusted dealer) with a fresh
random session id. The group key is the key's own public key, and an
existing BIP-32 chain code can be kept so derived addresses match. The
secret must be a canonical non-zero scalar. The documented follow-up is a
complete refresh, so the dealer-generated OT state is replaced.

C API tss_dkls_split_key; bindings for the DKLs23 split and the complete
refresh in Go, Node, Python and .NET (Node also gains Secp256r1ECDSA).
Tests in Rust, the FFI and every binding.

Binding fixes found by the new tests:
- .NET: the *_next calls marshalled out_complete as bool*, which .NET
  rejects, so no DKG, sign or refresh session could advance; pass a byte.
- Node: koffi registers struct names process-wide, so loading the
  bindings from a second test file failed; reuse the registered type.

Closes #10.
libtss::bolt3 wraps DKLs23's BOLT 3 derivations: bolt3_derive
(localpubkey-style), bolt3_derive_revocation (revocationpubkey, given the
revealed per-commitment secret) and their public-key-package variants.
Each party computes the tweak from public points; no raw scalar tweak is
exposed. Derived shares keep the base share's OT state, which the docs
call out for bans.

C API tss_bolt3_derive, tss_bolt3_derive_revocation and the
*_pubkey_package variants; bindings in Go, Node, Python and .NET. Tests
check the BOLT 3 Appendix E vectors and sign under the derived keys.

Replaces the raw tweaks proposed in #9.

Co-authored-by: José A.P <53834183+Jossec101@users.noreply.github.com>
- Abort culprits name the accused counterparty from the AbortReason, not
  the reporting party; local-state errors and session mismatches accuse
  no one. Affects DKG, signing and refresh.
- A failed DKLs23 refresh round ends the session (is_complete() is true).
- Refresh fragments and parsed secret bytes are zeroized on every exit.
- FFI handle getters clear the out buffer before failing.
- Node and Python validate DKLs23 thresholds before narrowing and wipe the
  secret on every path.
- Bindings document that a Taproot chain code matches an external CKDpub
  at depth 1 only; BOLT 3 docs no longer list remotepubkey as derived.
- Frozen pre-upgrade key-share fixtures, a tampered-proof refresh test and
  threshold tests.
- Importing a DKLs23 share checks that the party's threshold and share
  count match its public key package.
- BOLT 3 public-key-package derivation requires verifying shares indexed
  1..=n.
- The C API serializes the public key package before handing out split
  handles (DKLs23 and FROST), so a failure leaks no handles; Go frees the
  key share when a completed step's package fails to decode.
- Bindings wipe the per-commitment secret after the BOLT 3 revocation
  call; Python's FROST split wipes the secret on early errors too.
- React Native gains the Secp256r1ECDSA ciphersuite; WASM and spec docs
  match the implementation.
@canesin canesin changed the title build: use DKLs23 0.6.1 from crates.io build: DKLs23 0.6.1 and FROST 3.0.0; DKLs23 refresh, key import and BOLT 3 Oct 21, 2026
Instrumented debug builds run the refresh and import end-to-end tests
past tarpaulin's 300 s per-test timeout. cargo test still runs them;
cfg(tarpaulin) is declared for check-cfg.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants