From 3c44ce7d2b5338ab33b6160ff5e6851df3d1bd48 Mon Sep 17 00:00:00 2001 From: maclane Date: Sun, 7 Jun 2026 11:37:38 -0400 Subject: [PATCH] Remove unsupported UniFFI FROST material path --- ...ontext_bound_exchange_frost_native_test.go | 75 ----- .../signing/attempt_context_from_request.go | 29 +- .../attempt_context_from_request_test.go | 76 ++--- .../signing/dkg_group_pubkey_extraction.go | 78 ++--- .../dkg_group_pubkey_extraction_test.go | 113 +------ ...ffi_primitive_transitional_frost_native.go | 31 +- ...rimitive_transitional_frost_native_test.go | 30 ++ .../native_frost_dkg_engine_frost_native.go | 32 +- ...tive_frost_dkg_engine_frost_native_test.go | 152 +-------- ...ve_frost_dkg_engine_uniffi_frost_native.go | 157 ---------- .../native_frost_dkg_protocol_frost_native.go | 19 +- .../native_frost_engine_frost_native.go | 15 +- ...e_tbtc_signer_registration_frost_native.go | 16 +- ...c_signer_registration_frost_native_test.go | 8 +- ...native_frost_engine_uniffi_frost_native.go | 228 -------------- ...e_frost_engine_uniffi_frost_native_test.go | 289 ------------------ ...native_frost_protocol_frost_native_test.go | 281 ++--------------- ..._retry_executor_entry_frost_native_test.go | 15 +- ...y_executor_entry_frost_roast_retry_test.go | 13 +- pkg/tbtc/frost_dkg_execution_frost_native.go | 108 +++---- .../frost_dkg_execution_frost_native_test.go | 161 +++++++--- ...igning_native_backend_frost_native_test.go | 43 +-- pkg/tbtc/signing_schnorr_frost_native_test.go | 2 +- .../wallet_id_from_signer_frost_native.go | 35 ++- ...wallet_id_from_signer_frost_native_test.go | 34 +-- 25 files changed, 404 insertions(+), 1636 deletions(-) delete mode 100644 pkg/frost/signing/native_frost_dkg_engine_uniffi_frost_native.go delete mode 100644 pkg/frost/signing/native_frost_engine_uniffi_frost_native.go delete mode 100644 pkg/frost/signing/native_frost_engine_uniffi_frost_native_test.go diff --git a/pkg/frost/signing/attempt_context_bound_exchange_frost_native_test.go b/pkg/frost/signing/attempt_context_bound_exchange_frost_native_test.go index ff136e130a..13b9580884 100644 --- a/pkg/frost/signing/attempt_context_bound_exchange_frost_native_test.go +++ b/pkg/frost/signing/attempt_context_bound_exchange_frost_native_test.go @@ -4,7 +4,6 @@ package signing import ( "context" - "fmt" "math/big" "sync" "testing" @@ -42,80 +41,6 @@ func bindAttemptContextHashForExchangeTest( SetCurrentAttemptHandleForSession(sessionID, roast.AttemptHandle{}, ctx) } -func TestNativeFROSTSigning_BoundAttemptContextHashExchange(t *testing.T) { - ResetSessionHandleRegistryForTest() - t.Cleanup(ResetSessionHandleRegistryForTest) - - RegisterNativeFROSTSigningEngine(&deterministicNativeFROSTSigningEngine{}) - t.Cleanup(UnregisterNativeFROSTSigningEngine) - - provider := local.Connect() - channel, err := provider.BroadcastChannelFor( - "native-frost-signing-bound-attempt-context-test", - ) - if err != nil { - t.Fatalf("failed creating broadcast channel: [%v]", err) - } - - primitive := &buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive{} - primitive.RegisterUnmarshallers(channel) - - sessionID := "native-frost-bound-attempt-context" - includedMembers := []group.MemberIndex{1, 2, 3} - bindAttemptContextHashForExchangeTest(t, sessionID, includedMembers) - - requests := make([]*NativeExecutionFFISigningRequest, len(includedMembers)) - for i, memberIndex := range includedMembers { - requests[i], err = newNativeFROSTSigningRequestWithSessionForTest( - memberIndex, - includedMembers, - channel, - len(includedMembers), - sessionID, - ) - if err != nil { - t.Fatalf( - "failed preparing request for member [%v]: [%v]", - memberIndex, - err, - ) - } - } - - ctx, cancelCtx := context.WithTimeout(context.Background(), 10*time.Second) - defer cancelCtx() - - signingErrors := make(chan error, len(requests)) - var wg sync.WaitGroup - wg.Add(len(requests)) - - for _, request := range requests { - go func(signingRequest *NativeExecutionFFISigningRequest) { - defer wg.Done() - - signature, signErr := primitive.Sign(ctx, nil, signingRequest) - if signErr != nil { - signingErrors <- signErr - return - } - if signature == nil { - signingErrors <- fmt.Errorf("nil signature") - return - } - signingErrors <- nil - }(request) - } - - wg.Wait() - close(signingErrors) - - for signErr := range signingErrors { - if signErr != nil { - t.Fatalf("unexpected signing error: [%v]", signErr) - } - } -} - func TestBuildTaggedTBTCSignerBootstrapCoarseRound_BoundAttemptContextHashExchange( t *testing.T, ) { diff --git a/pkg/frost/signing/attempt_context_from_request.go b/pkg/frost/signing/attempt_context_from_request.go index 5e33d79ab4..70274d895c 100644 --- a/pkg/frost/signing/attempt_context_from_request.go +++ b/pkg/frost/signing/attempt_context_from_request.go @@ -7,7 +7,6 @@ import ( "fmt" "math/big" - "github.com/keep-network/keep-core/pkg/frost" "github.com/keep-network/keep-core/pkg/frost/roast/attempt" ) @@ -35,13 +34,8 @@ var ErrAttemptContextConstruction = errors.New( // the tagged payload, so padding is a no-op. // - DkgGroupPublicKey is extracted via // ExtractDkgGroupPublicKeyFromMaterial. -// - KeyGroupID is derived format-aware: -// FrostUniFFIV2: HASH160(0x02 || xOnlyOutputKey) -- matches -// RFC-20's compatibility-alias scheme for legacy -// 20-byte wallet key hashes. -// FrostTBTCSignerV1: the raw KeyGroup string identifier from -// the tbtc-signer material, which is already a canonical -// per-group handle. +// - KeyGroupID is derived from the raw FrostTBTCSignerV1 KeyGroup string +// identifier, which is already a canonical per-group handle. // - AttemptSeed = SHA256(DkgGroupPublicKey || SessionID || // MessageDigest) per RFC-21 Decision 2. // @@ -55,7 +49,7 @@ var ErrAttemptContextConstruction = errors.New( // Returns ErrAttemptContextConstruction-wrapped errors for any // failure during the construction. Returns ErrUnsupportedSignerMaterialFormat // (via errors.Is) when the material's format is not extractable -// (e.g. FrostUniFFIV1 today). +// (e.g. FrostUniFFIV1 or unsupported FrostUniFFIV2 today). func BuildAttemptContextFromRequest( request *NativeExecutionFFISigningRequest, ) (attempt.AttemptContext, error) { @@ -151,11 +145,6 @@ func BuildAttemptContextFromRequest( // from the signer material plus the already-extracted DKG group // public key. The derivation is format-aware: // -// - FrostUniFFIV2: HASH160(0x02 || dkgPub) -- the compressed -// 33-byte form prefixed with 0x02 matches the legacy -// compatibility-alias scheme RFC-20 introduced for 20-byte -// wallet pub-key-hashes. dkgPub here is the 32-byte x-only -// output key. // - FrostTBTCSignerV1: the raw KeyGroup string from the tbtc- // signer material. That string is the canonical handle. // @@ -168,18 +157,6 @@ func deriveKeyGroupID( dkgPub []byte, ) (string, error) { switch signerMaterial.Format { - case NativeSignerMaterialFormatFrostUniFFIV2: - if len(dkgPub) != frost.OutputKeySize { - return "", fmt.Errorf( - "derive key group id: FrostUniFFIV2 x-only key length %d, expected %d", - len(dkgPub), - frost.OutputKeySize, - ) - } - var outputKey frost.OutputKey - copy(outputKey[:], dkgPub) - alias := frost.WalletPublicKeyHashCompatibilityAlias(outputKey) - return fmt.Sprintf("%x", alias[:]), nil case NativeSignerMaterialFormatFrostTBTCSignerV1: payload, err := decodeBuildTaggedTBTCSignerMaterialPayload(signerMaterial) if err != nil { diff --git a/pkg/frost/signing/attempt_context_from_request_test.go b/pkg/frost/signing/attempt_context_from_request_test.go index e78adecf14..bf08910a94 100644 --- a/pkg/frost/signing/attempt_context_from_request_test.go +++ b/pkg/frost/signing/attempt_context_from_request_test.go @@ -11,12 +11,11 @@ import ( "strings" "testing" - "github.com/keep-network/keep-core/pkg/frost" "github.com/keep-network/keep-core/pkg/frost/roast/attempt" "github.com/keep-network/keep-core/pkg/protocol/group" ) -func newTestRequestWithUniFFIV2Material(t *testing.T, attemptNumber uint) *NativeExecutionFFISigningRequest { +func newTestRequestWithUnsupportedUniFFIV2Material(t *testing.T, attemptNumber uint) *NativeExecutionFFISigningRequest { t.Helper() const hexKey = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" payload, _ := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ @@ -67,48 +66,6 @@ func newTestRequestWithTBTCSignerV1Material(t *testing.T, attemptNumber uint) *N } } -func TestBuildAttemptContextFromRequest_UniFFIV2_HappyPath(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) - ctx, err := BuildAttemptContextFromRequest(req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if ctx.SessionID != req.SessionID { - t.Fatalf("session id: got %q want %q", ctx.SessionID, req.SessionID) - } - if ctx.AttemptNumber != 0 { - t.Fatalf("attempt number: got %d, want 0 (Attempt.Number=1 maps to 0-based 0)", ctx.AttemptNumber) - } - if len(ctx.IncludedSet) != 5 { - t.Fatalf("included set: got %d, want 5", len(ctx.IncludedSet)) - } - if len(ctx.TransientlyParked) != 0 { - t.Fatalf("parked: got %d, want 0 (Phase 6 ships attempt-zero shape)", len(ctx.TransientlyParked)) - } -} - -func TestBuildAttemptContextFromRequest_UniFFIV2_KeyGroupIDDerivation(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) - ctx, err := BuildAttemptContextFromRequest(req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - // Reproduce the expected derivation: HASH160(0x02 || dkgPub). - dkgPub, _ := ExtractDkgGroupPublicKeyFromMaterial(req.SignerMaterial) - var outputKey frost.OutputKey - copy(outputKey[:], dkgPub) - want := fmt.Sprintf("%x", frost.WalletPublicKeyHashCompatibilityAlias(outputKey)) - if ctx.KeyGroupID != want { - t.Fatalf( - "key group id: got %s, want %s", - ctx.KeyGroupID, want, - ) - } - if len(ctx.KeyGroupID) != 40 { - t.Fatalf("key group id hex length: got %d, want 40 (20 bytes)", len(ctx.KeyGroupID)) - } -} - func TestBuildAttemptContextFromRequest_TBTCSignerV1_KeyGroupIDIsRawIdentifier(t *testing.T) { req := newTestRequestWithTBTCSignerV1Material(t, 1) ctx, err := BuildAttemptContextFromRequest(req) @@ -124,6 +81,17 @@ func TestBuildAttemptContextFromRequest_TBTCSignerV1_KeyGroupIDIsRawIdentifier(t } } +func TestBuildAttemptContextFromRequest_UnsupportedUniFFIV2Rejected(t *testing.T) { + req := newTestRequestWithUnsupportedUniFFIV2Material(t, 1) + _, err := BuildAttemptContextFromRequest(req) + if !errors.Is(err, ErrUnsupportedSignerMaterialFormat) { + t.Fatalf("expected ErrUnsupportedSignerMaterialFormat, got %v", err) + } + if !strings.Contains(err.Error(), "unsupported") { + t.Fatalf("error should mention unsupported format; got %v", err) + } +} + func TestBuildAttemptContextFromRequest_RejectsNilRequest(t *testing.T) { _, err := BuildAttemptContextFromRequest(nil) if !errors.Is(err, ErrAttemptContextConstruction) { @@ -132,7 +100,7 @@ func TestBuildAttemptContextFromRequest_RejectsNilRequest(t *testing.T) { } func TestBuildAttemptContextFromRequest_RejectsNilMessage(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) + req := newTestRequestWithTBTCSignerV1Material(t, 1) req.Message = nil _, err := BuildAttemptContextFromRequest(req) if err == nil { @@ -144,7 +112,7 @@ func TestBuildAttemptContextFromRequest_RejectsNilMessage(t *testing.T) { } func TestBuildAttemptContextFromRequest_RejectsNilSignerMaterial(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) + req := newTestRequestWithTBTCSignerV1Material(t, 1) req.SignerMaterial = nil _, err := BuildAttemptContextFromRequest(req) if err == nil { @@ -156,7 +124,7 @@ func TestBuildAttemptContextFromRequest_RejectsNilSignerMaterial(t *testing.T) { } func TestBuildAttemptContextFromRequest_RejectsNilAttempt(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) + req := newTestRequestWithTBTCSignerV1Material(t, 1) req.Attempt = nil _, err := BuildAttemptContextFromRequest(req) if err == nil { @@ -165,7 +133,7 @@ func TestBuildAttemptContextFromRequest_RejectsNilAttempt(t *testing.T) { } func TestBuildAttemptContextFromRequest_RejectsZeroAttemptNumber(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 0) + req := newTestRequestWithTBTCSignerV1Material(t, 0) _, err := BuildAttemptContextFromRequest(req) if err == nil { t.Fatal("expected error for zero attempt number") @@ -176,7 +144,7 @@ func TestBuildAttemptContextFromRequest_RejectsZeroAttemptNumber(t *testing.T) { } func TestBuildAttemptContextFromRequest_PropagatesExtractionErrors(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) + req := newTestRequestWithTBTCSignerV1Material(t, 1) req.SignerMaterial = &NativeSignerMaterial{ Format: NativeSignerMaterialFormatFrostUniFFIV1, Payload: []byte("{}"), @@ -201,7 +169,7 @@ func TestBuildAttemptContextFromRequest_AttemptNumberIsZeroBased(t *testing.T) { } for _, tc := range cases { t.Run(fmt.Sprintf("legacy=%d", tc.legacyNumber), func(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, tc.legacyNumber) + req := newTestRequestWithTBTCSignerV1Material(t, tc.legacyNumber) ctx, err := BuildAttemptContextFromRequest(req) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -240,7 +208,7 @@ func TestMessageDigestFromBigInt_RejectsLongBigInts(t *testing.T) { } func TestBuildAttemptContextFromRequest_DeterministicAcrossInvocations(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) + req := newTestRequestWithTBTCSignerV1Material(t, 1) a, err := BuildAttemptContextFromRequest(req) if err != nil { t.Fatalf("first: %v", err) @@ -258,7 +226,7 @@ func TestBuildAttemptContextFromRequest_DeterministicAcrossInvocations(t *testin } func TestBuildAttemptContextFromRequest_HashChangesWhenMessageDigestChanges(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) + req := newTestRequestWithTBTCSignerV1Material(t, 1) a, _ := BuildAttemptContextFromRequest(req) req.Message = new(big.Int).SetBytes([]byte{0x99, 0x88, 0x77}) b, _ := BuildAttemptContextFromRequest(req) @@ -268,9 +236,9 @@ func TestBuildAttemptContextFromRequest_HashChangesWhenMessageDigestChanges(t *t } func TestBuildAttemptContextFromRequest_HashChangesWhenIncludedSetChanges(t *testing.T) { - req := newTestRequestWithUniFFIV2Material(t, 1) + req := newTestRequestWithTBTCSignerV1Material(t, 1) a, _ := BuildAttemptContextFromRequest(req) - req.Attempt.IncludedMembersIndexes = []group.MemberIndex{1, 2, 3} + req.Attempt.IncludedMembersIndexes = []group.MemberIndex{1, 2, 4} b, _ := BuildAttemptContextFromRequest(req) if a.Hash() == b.Hash() { t.Fatal("hash must change when included set changes") diff --git a/pkg/frost/signing/dkg_group_pubkey_extraction.go b/pkg/frost/signing/dkg_group_pubkey_extraction.go index 7c2c91cf4e..db4b3880ae 100644 --- a/pkg/frost/signing/dkg_group_pubkey_extraction.go +++ b/pkg/frost/signing/dkg_group_pubkey_extraction.go @@ -13,10 +13,11 @@ import ( // ErrUnsupportedSignerMaterialFormat is returned by // ExtractDkgGroupPublicKeyFromMaterial when the material's Format -// field names a signer-material variant the helper cannot extract -// a DKG group public key from. The current implementation accepts -// FrostUniFFIV2 and FrostTBTCSignerV1; FrostUniFFIV1 is rejected -// because the legacy bridge format does not expose the group key. +// field names a signer-material variant the helper cannot extract a DKG group +// public key from. The current implementation accepts FrostTBTCSignerV1; +// FrostUniFFIV1 is rejected because the legacy bridge format does not expose +// the group key, and unsupported FrostUniFFIV2 material is rejected because it +// cannot support Taproot-tweaked deposit sweep signatures. // // Per RFC-21 Phase-6 Resolved Decision: the Phase 7 manifest flip // is gated on verified migration off V1 across production signers, @@ -34,26 +35,21 @@ var ErrUnsupportedSignerMaterialFormat = errors.New( // // Format handling: // -// - FrostUniFFIV2: decode payload as nativeFROSTUniFFIV2SignerMaterial; -// hex-decode PublicKeyPackage.VerifyingKey. This is the x-only -// output key produced by the native FROST DKG. -// // - FrostTBTCSignerV1: decode payload as NativeTBTCSignerMaterialPayload; // return the raw bytes of the KeyGroup identifier. The tbtc-signer // engine treats KeyGroup as the canonical handle for the FROST // key group; every honest signer running the same tbtc-signer // build agrees on its bytes. // -// - FrostUniFFIV1: returns ErrUnsupportedSignerMaterialFormat. -// V1 material is the legacy bridge format that does not carry -// the group public key in a form Phase 6 can extract. +// - FrostUniFFIV1 and FrostUniFFIV2: return +// ErrUnsupportedSignerMaterialFormat. V1 material is the legacy bridge +// format that does not carry the group key in a form Phase 6 can extract. +// V2 material is unsupported in favor of FrostTBTCSignerV1. // // Callers MUST use the returned bytes only as the // DkgGroupPublicKey input to attempt.DeriveAttemptSeed; the bytes -// are not interchangeable across format boundaries (a UniFFIV2 key -// and a TBTCSignerV1 key for the "same" logical group produce -// different bytes -- they are different formats). Production -// signing groups must run on a single uniform format. +// are not interchangeable across format boundaries. Production signing groups +// must use FrostTBTCSignerV1 material. func ExtractDkgGroupPublicKeyFromMaterial( signerMaterial *NativeSignerMaterial, ) ([]byte, error) { @@ -63,16 +59,20 @@ func ExtractDkgGroupPublicKeyFromMaterial( ) } switch signerMaterial.Format { - case NativeSignerMaterialFormatFrostUniFFIV2: - return extractDkgGroupPublicKeyFromUniFFIV2(signerMaterial) case NativeSignerMaterialFormatFrostTBTCSignerV1: return extractDkgGroupPublicKeyFromTBTCSignerV1(signerMaterial) case NativeSignerMaterialFormatFrostUniFFIV1: return nil, fmt.Errorf( - "%w: %s (migrate to %s or %s before enabling ROAST retry)", + "%w: %s (migrate to %s before enabling ROAST retry)", + ErrUnsupportedSignerMaterialFormat, + signerMaterial.Format, + NativeSignerMaterialFormatFrostTBTCSignerV1, + ) + case NativeSignerMaterialFormatFrostUniFFIV2: + return nil, fmt.Errorf( + "%w: %s is unsupported; use %s", ErrUnsupportedSignerMaterialFormat, signerMaterial.Format, - NativeSignerMaterialFormatFrostUniFFIV2, NativeSignerMaterialFormatFrostTBTCSignerV1, ) default: @@ -94,8 +94,6 @@ func ExtractTaprootOutputKeyFromMaterial( } switch signerMaterial.Format { - case NativeSignerMaterialFormatFrostUniFFIV2: - return extractDkgGroupPublicKeyFromUniFFIV2(signerMaterial) case NativeSignerMaterialFormatFrostTBTCSignerV1: return extractTaprootOutputKeyFromTBTCSignerV1(signerMaterial) default: @@ -106,44 +104,6 @@ func ExtractTaprootOutputKeyFromMaterial( } } -func extractDkgGroupPublicKeyFromUniFFIV2( - signerMaterial *NativeSignerMaterial, -) ([]byte, error) { - decoded, err := decodeNativeFROSTUniFFIV2SignerMaterial(signerMaterial) - if err != nil { - return nil, fmt.Errorf( - "dkg group public key: decode FrostUniFFIV2: %w", - err, - ) - } - if decoded.PublicKeyPackage == nil { - return nil, fmt.Errorf( - "dkg group public key: FrostUniFFIV2 public key package is nil", - ) - } - verifyingKey := decoded.PublicKeyPackage.VerifyingKey - if verifyingKey == "" { - return nil, fmt.Errorf( - "dkg group public key: FrostUniFFIV2 verifying key is empty", - ) - } - raw, err := hex.DecodeString(verifyingKey) - if err != nil { - return nil, fmt.Errorf( - "dkg group public key: FrostUniFFIV2 verifying key is not hex: %w", - err, - ) - } - if len(raw) != frost.OutputKeySize { - return nil, fmt.Errorf( - "dkg group public key: FrostUniFFIV2 verifying key must be %d bytes, got %d", - frost.OutputKeySize, - len(raw), - ) - } - return raw, nil -} - func extractDkgGroupPublicKeyFromTBTCSignerV1( signerMaterial *NativeSignerMaterial, ) ([]byte, error) { diff --git a/pkg/frost/signing/dkg_group_pubkey_extraction_test.go b/pkg/frost/signing/dkg_group_pubkey_extraction_test.go index b8570a92e5..5fae38a805 100644 --- a/pkg/frost/signing/dkg_group_pubkey_extraction_test.go +++ b/pkg/frost/signing/dkg_group_pubkey_extraction_test.go @@ -18,15 +18,14 @@ func TestExtractDkgGroupPublicKey_RejectsNilMaterial(t *testing.T) { } } -func TestExtractDkgGroupPublicKey_FrostUniFFIV2_HexDecodes(t *testing.T) { - const hexKey = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" +func TestExtractDkgGroupPublicKey_FrostUniFFIV2_ReturnsUnsupportedSentinel(t *testing.T) { payload, err := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ KeyPackage: &NativeFROSTKeyPackage{ Identifier: "id-1", Data: []byte{0x01}, }, PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingKey: hexKey, + VerifyingKey: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", }, }) if err != nil { @@ -36,88 +35,39 @@ func TestExtractDkgGroupPublicKey_FrostUniFFIV2_HexDecodes(t *testing.T) { Format: NativeSignerMaterialFormatFrostUniFFIV2, Payload: payload, } - got, err := ExtractDkgGroupPublicKeyFromMaterial(mat) - if err != nil { - t.Fatalf("extract: %v", err) - } - want, _ := hex.DecodeString(hexKey) - if !bytes.Equal(got, want) { - t.Fatalf( - "hex decode mismatch: got %x, want %x", - got, want, - ) + _, err = ExtractDkgGroupPublicKeyFromMaterial(mat) + if !errors.Is(err, ErrUnsupportedSignerMaterialFormat) { + t.Fatalf("expected ErrUnsupportedSignerMaterialFormat, got %v", err) } - if len(got) != 32 { - t.Fatalf("expected 32 bytes, got %d", len(got)) + if !strings.Contains(err.Error(), "unsupported") { + t.Fatalf("error must mention unsupported format; got %v", err) } } -func TestExtractDkgGroupPublicKey_FrostUniFFIV2_RejectsEmptyVerifyingKey(t *testing.T) { - payload, _ := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ +func TestExtractTaprootOutputKey_FrostUniFFIV2_ReturnsUnsupported(t *testing.T) { + payload, err := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ KeyPackage: &NativeFROSTKeyPackage{ Identifier: "id-1", Data: []byte{0x01}, }, PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingKey: "", + VerifyingKey: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", }, }) - mat := &NativeSignerMaterial{ - Format: NativeSignerMaterialFormatFrostUniFFIV2, - Payload: payload, - } - // The pre-existing decodeNativeFROSTUniFFIV2SignerMaterial - // validator may reject this before our helper sees it; either - // way an error must be returned. - _, err := ExtractDkgGroupPublicKeyFromMaterial(mat) - if err == nil { - t.Fatal("expected error for empty VerifyingKey") + if err != nil { + t.Fatalf("marshal: %v", err) } -} - -func TestExtractDkgGroupPublicKey_FrostUniFFIV2_RejectsNonHexVerifyingKey(t *testing.T) { - payload, _ := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ - KeyPackage: &NativeFROSTKeyPackage{ - Identifier: "id-1", - Data: []byte{0x01}, - }, - PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingKey: "not-hex-zzz!", - }, - }) mat := &NativeSignerMaterial{ Format: NativeSignerMaterialFormatFrostUniFFIV2, Payload: payload, } - _, err := ExtractDkgGroupPublicKeyFromMaterial(mat) - if err == nil { - t.Fatal("expected error for non-hex VerifyingKey") - } - if !strings.Contains(err.Error(), "not hex") { - t.Fatalf("error must mention hex problem; got %v", err) - } -} -func TestExtractDkgGroupPublicKey_FrostUniFFIV2_RejectsWrongLength(t *testing.T) { - payload, _ := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ - KeyPackage: &NativeFROSTKeyPackage{ - Identifier: "id-1", - Data: []byte{0x01}, - }, - PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingKey: strings.Repeat("11", 31), - }, - }) - mat := &NativeSignerMaterial{ - Format: NativeSignerMaterialFormatFrostUniFFIV2, - Payload: payload, - } - _, err := ExtractDkgGroupPublicKeyFromMaterial(mat) + _, err = ExtractTaprootOutputKeyFromMaterial(mat) if err == nil { - t.Fatal("expected error for wrong-length VerifyingKey") + t.Fatal("expected unsupported V2 taproot output key rejection") } - if !strings.Contains(err.Error(), "must be 32 bytes") { - t.Fatalf("error must mention length problem; got %v", err) + if !strings.Contains(err.Error(), "unsupported signer-material format") { + t.Fatalf("error must mention unsupported format; got %v", err) } } @@ -302,34 +252,3 @@ func TestExtractDkgGroupPublicKey_UnknownFormat_ReturnsUnsupportedSentinel(t *te t.Fatalf("error must mention the unknown format; got %v", err) } } - -func TestExtractDkgGroupPublicKey_FrostUniFFIV2_GoldenFixture(t *testing.T) { - // Lock the canonical byte output for a specific hex input. If a - // future change to extractDkgGroupPublicKeyFromUniFFIV2 alters - // the result, this test catches the drift at code review. - const hexKey = "deadbeefcafebabe0000000000000000000000000000000000000000000000ff" - payload, _ := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ - KeyPackage: &NativeFROSTKeyPackage{ - Identifier: "fixture", - Data: []byte{0xFF}, - }, - PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingKey: hexKey, - }, - }) - mat := &NativeSignerMaterial{ - Format: NativeSignerMaterialFormatFrostUniFFIV2, - Payload: payload, - } - got, err := ExtractDkgGroupPublicKeyFromMaterial(mat) - if err != nil { - t.Fatalf("extract: %v", err) - } - want, _ := hex.DecodeString(hexKey) - if !bytes.Equal(got, want) { - t.Fatalf( - "golden fixture mismatch: got %x, want %x", - got, want, - ) - } -} diff --git a/pkg/frost/signing/native_ffi_primitive_transitional_frost_native.go b/pkg/frost/signing/native_ffi_primitive_transitional_frost_native.go index e481b06785..4b6fb1f88a 100644 --- a/pkg/frost/signing/native_ffi_primitive_transitional_frost_native.go +++ b/pkg/frost/signing/native_ffi_primitive_transitional_frost_native.go @@ -34,11 +34,13 @@ func defaultNativeExecutionFFISigningPrimitiveProviderForBuild() ( } // buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive is a -// transitional primitive that executes native two-round FROST when -// `frost-uniffi-v2` signer material is provided, and preserves legacy bridge -// execution for `frost-uniffi-v1` payloads. `frost-tbtc-signer-v1` uses the -// coarse signing flow for bootstrap engine versions and falls back to legacy -// signing for unsupported or failed coarse-path executions. +// transitional primitive that preserves legacy bridge execution for +// `frost-uniffi-v1` payloads. `frost-tbtc-signer-v1` uses the coarse signing +// flow for bootstrap engine versions and falls back to legacy signing for +// unsupported or failed coarse-path executions. Unsupported +// `frost-uniffi-v2` material is rejected explicitly because it cannot produce +// Taproot-tweaked signatures; accepting it would allow new deposits to a +// wallet that cannot sweep them. type buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive struct{} const buildTaggedTBTCSignerVersionPrefix = "tbtc-signer/" @@ -156,19 +158,11 @@ func (btlcnnefsp *buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive) switch request.SignerMaterial.Format { case NativeSignerMaterialFormatFrostUniFFIV2: - nativeSignerMaterial, err := decodeNativeFROSTUniFFIV2SignerMaterial( - request.SignerMaterial, - ) - if err != nil { - return nil, err - } - - return executeNativeFROSTSigning( - ctx, - logger, - request, - currentNativeFROSTSigningEngine(), - nativeSignerMaterial, + return nil, fmt.Errorf( + "%w: unsupported UniFFI FROST signer material format [%s]; it cannot sweep Taproot deposits; use [%s]", + ErrUnsupportedSignerMaterialFormat, + NativeSignerMaterialFormatFrostUniFFIV2, + NativeSignerMaterialFormatFrostTBTCSignerV1, ) case NativeSignerMaterialFormatFrostUniFFIV1: @@ -1280,7 +1274,6 @@ func (btlcnnefsp *buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive) channel net.BroadcastChannel, ) { registerBuildTaggedTBTCSignerUnmarshallers(channel) - registerNativeFROSTSigningUnmarshallers(channel) legacySigning.RegisterUnmarshallers(channel) } diff --git a/pkg/frost/signing/native_ffi_primitive_transitional_frost_native_test.go b/pkg/frost/signing/native_ffi_primitive_transitional_frost_native_test.go index 0783c89b37..8b78d39f5d 100644 --- a/pkg/frost/signing/native_ffi_primitive_transitional_frost_native_test.go +++ b/pkg/frost/signing/native_ffi_primitive_transitional_frost_native_test.go @@ -359,6 +359,36 @@ func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_Vali } } +func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_RejectsUnsupportedUniFFIV2Material( + t *testing.T, +) { + primitive := &buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive{} + + _, err := primitive.Sign(nil, nil, &NativeExecutionFFISigningRequest{ + Message: big.NewInt(123), + SignerMaterial: &NativeSignerMaterial{ + Format: NativeSignerMaterialFormatFrostUniFFIV2, + Payload: []byte{0x01}, + }, + }) + if err == nil { + t.Fatal("expected unsupported material error") + } + if !errors.Is(err, ErrUnsupportedSignerMaterialFormat) { + t.Fatalf( + "unexpected error\nexpected: [%v]\nactual: [%v]", + ErrUnsupportedSignerMaterialFormat, + err, + ) + } + if errors.Is(err, ErrNativeCryptographyUnavailable) { + t.Fatalf( + "unsupported signer material should not be reported as unavailable native cryptography: [%v]", + err, + ) + } +} + func TestDecodeBuildTaggedLegacyPrivateKeyShare(t *testing.T) { fixtures, err := tecdsatest.LoadPrivateKeyShareTestFixtures(5) if err != nil { diff --git a/pkg/frost/signing/native_frost_dkg_engine_frost_native.go b/pkg/frost/signing/native_frost_dkg_engine_frost_native.go index 658426c7ca..b30b2048f4 100644 --- a/pkg/frost/signing/native_frost_dkg_engine_frost_native.go +++ b/pkg/frost/signing/native_frost_dkg_engine_frost_native.go @@ -2,10 +2,7 @@ package signing -import ( - "encoding/json" - "fmt" -) +import "fmt" // NativeFROSTDKGRound1Package is the public package broadcast during FROST DKG // round one. @@ -58,30 +55,19 @@ type NativeFROSTDKGResult struct { PublicKeyPackage *NativeFROSTPublicKeyPackage `json:"publicKeyPackage"` } -// SignerMaterial converts the DKG output into the existing FrostUniFFIV2 -// signer-material envelope used by native FROST signing. +// SignerMaterial rejects the unsupported generic UniFFI FROST DKG output. +// FROST wallet material must be persisted through the tbtc-signer engine so +// Taproot tweaked signing is available for deposit sweeps. func (nfdkg *NativeFROSTDKGResult) SignerMaterial() (*NativeSignerMaterial, error) { if nfdkg == nil { return nil, fmt.Errorf("native FROST DKG result is nil") } - material := &nativeFROSTUniFFIV2SignerMaterial{ - KeyPackage: nfdkg.KeyPackage, - PublicKeyPackage: nfdkg.PublicKeyPackage, - } - if err := material.validate(); err != nil { - return nil, err - } - - payload, err := json.Marshal(material) - if err != nil { - return nil, fmt.Errorf("cannot marshal native FROST DKG signer material: [%w]", err) - } - - return &NativeSignerMaterial{ - Format: NativeSignerMaterialFormatFrostUniFFIV2, - Payload: payload, - }, nil + return nil, fmt.Errorf( + "native FROST DKG result cannot be persisted as unsupported [%s] signer material; use [%s]", + NativeSignerMaterialFormatFrostUniFFIV2, + NativeSignerMaterialFormatFrostTBTCSignerV1, + ) } // NativeFROSTDKGEngine executes the cryptographic primitives for the three diff --git a/pkg/frost/signing/native_frost_dkg_engine_frost_native_test.go b/pkg/frost/signing/native_frost_dkg_engine_frost_native_test.go index c0e7a0b579..e6a86c147f 100644 --- a/pkg/frost/signing/native_frost_dkg_engine_frost_native_test.go +++ b/pkg/frost/signing/native_frost_dkg_engine_frost_native_test.go @@ -130,66 +130,6 @@ func (mnfdkg *mockNativeFROSTDKGEngine) Part3( return nil, nil } -type mockUniFFINativeFROSTDKGBridge struct { - part1Called bool - part2Called bool - part3Called bool -} - -func (munfdkgb *mockUniFFINativeFROSTDKGBridge) Part1( - participantIdentifier string, - maxSigners uint16, - minSigners uint16, -) (*NativeFROSTDKGPart1Result, error) { - munfdkgb.part1Called = true - - return &NativeFROSTDKGPart1Result{ - SecretPackage: &NativeFROSTDKGRound1SecretPackage{Data: []byte{0x01}}, - Package: &NativeFROSTDKGRound1Package{ - Identifier: participantIdentifier, - Data: []byte{byte(maxSigners), byte(minSigners)}, - }, - }, nil -} - -func (munfdkgb *mockUniFFINativeFROSTDKGBridge) Part2( - secretPackage *NativeFROSTDKGRound1SecretPackage, - round1Packages []*NativeFROSTDKGRound1Package, -) (*NativeFROSTDKGPart2Result, error) { - munfdkgb.part2Called = true - - return &NativeFROSTDKGPart2Result{ - SecretPackage: &NativeFROSTDKGRound2SecretPackage{Data: []byte{0x02}}, - Packages: []*NativeFROSTDKGRound2Package{ - { - Identifier: round1Packages[0].Identifier, - Data: append([]byte{}, secretPackage.Data...), - }, - }, - }, nil -} - -func (munfdkgb *mockUniFFINativeFROSTDKGBridge) Part3( - secretPackage *NativeFROSTDKGRound2SecretPackage, - round1Packages []*NativeFROSTDKGRound1Package, - round2Packages []*NativeFROSTDKGRound2Package, -) (*NativeFROSTDKGResult, error) { - munfdkgb.part3Called = true - - return &NativeFROSTDKGResult{ - KeyPackage: &NativeFROSTKeyPackage{ - Identifier: round2Packages[0].SenderIdentifier, - Data: append([]byte{}, secretPackage.Data...), - }, - PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingShares: map[string]string{ - round1Packages[0].Identifier: "share", - }, - VerifyingKey: "1111111111111111111111111111111111111111111111111111111111111111", - }, - }, nil -} - func TestRegisterNativeFROSTDKGEngineRejectsNil(t *testing.T) { UnregisterNativeFROSTDKGEngine() t.Cleanup(UnregisterNativeFROSTDKGEngine) @@ -214,57 +154,6 @@ func TestRegisterNativeFROSTDKGEngine(t *testing.T) { } } -func TestNewUniFFINativeFROSTDKGEngine_NilBridge(t *testing.T) { - _, err := newUniFFINativeFROSTDKGEngine(nil) - if err == nil { - t.Fatal("expected error") - } -} - -func TestUniFFINativeFROSTDKGEngine(t *testing.T) { - bridge := &mockUniFFINativeFROSTDKGBridge{} - engine, err := newUniFFINativeFROSTDKGEngine(bridge) - if err != nil { - t.Fatalf("unexpected constructor error: [%v]", err) - } - - part1, err := engine.Part1("participant-1", 3, 2) - if err != nil { - t.Fatalf("unexpected part1 error: [%v]", err) - } - - part2, err := engine.Part2( - part1.SecretPackage, - []*NativeFROSTDKGRound1Package{ - {Identifier: "participant-2", Data: []byte{0x22}}, - }, - ) - if err != nil { - t.Fatalf("unexpected part2 error: [%v]", err) - } - - _, err = engine.Part3( - part2.SecretPackage, - []*NativeFROSTDKGRound1Package{ - {Identifier: "participant-2", Data: []byte{0x22}}, - }, - []*NativeFROSTDKGRound2Package{ - { - Identifier: "participant-1", - SenderIdentifier: "participant-2", - Data: []byte{0x33}, - }, - }, - ) - if err != nil { - t.Fatalf("unexpected part3 error: [%v]", err) - } - - if !bridge.part1Called || !bridge.part2Called || !bridge.part3Called { - t.Fatal("expected all bridge parts to be called") - } -} - func TestExecuteNativeFROSTDKG(t *testing.T) { const channelName = "native-frost-dkg-test" ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -571,7 +460,7 @@ func nativeFROSTDKGTestMembership( ) } -func TestNativeFROSTDKGResultSignerMaterial(t *testing.T) { +func TestNativeFROSTDKGResultSignerMaterialRejectsUnsupportedFormat(t *testing.T) { dkgResult := &NativeFROSTDKGResult{ KeyPackage: &NativeFROSTKeyPackage{ Identifier: "0000000000000000000000000000000000000000000000000000000000000001", @@ -585,40 +474,11 @@ func TestNativeFROSTDKGResultSignerMaterial(t *testing.T) { }, } - signerMaterial, err := dkgResult.SignerMaterial() - if err != nil { - t.Fatalf("unexpected signer material error: [%v]", err) - } - - if signerMaterial.Format != NativeSignerMaterialFormatFrostUniFFIV2 { - t.Fatalf( - "unexpected signer material format\nexpected: [%s]\nactual: [%s]", - NativeSignerMaterialFormatFrostUniFFIV2, - signerMaterial.Format, - ) - } - - extracted, err := ExtractDkgGroupPublicKeyFromMaterial(signerMaterial) - if err != nil { - t.Fatalf("unexpected DKG public-key extraction error: [%v]", err) - } - - expected := "1111111111111111111111111111111111111111111111111111111111111111" - if actual := stringHex(extracted); actual != expected { - t.Fatalf( - "unexpected extracted DKG output key\nexpected: [%s]\nactual: [%s]", - expected, - actual, - ) + _, err := dkgResult.SignerMaterial() + if err == nil { + t.Fatal("expected unsupported signer material error") } -} - -func stringHex(data []byte) string { - const hexChars = "0123456789abcdef" - result := make([]byte, len(data)*2) - for i, b := range data { - result[i*2] = hexChars[b>>4] - result[i*2+1] = hexChars[b&0x0f] + if !strings.Contains(err.Error(), NativeSignerMaterialFormatFrostUniFFIV2) { + t.Fatalf("error should mention unsupported format: [%v]", err) } - return string(result) } diff --git a/pkg/frost/signing/native_frost_dkg_engine_uniffi_frost_native.go b/pkg/frost/signing/native_frost_dkg_engine_uniffi_frost_native.go deleted file mode 100644 index 4db0e87ee7..0000000000 --- a/pkg/frost/signing/native_frost_dkg_engine_uniffi_frost_native.go +++ /dev/null @@ -1,157 +0,0 @@ -//go:build frost_native - -package signing - -import "fmt" - -type uniFFINativeFROSTDKGBridge interface { - Part1( - participantIdentifier string, - maxSigners uint16, - minSigners uint16, - ) (*NativeFROSTDKGPart1Result, error) - Part2( - secretPackage *NativeFROSTDKGRound1SecretPackage, - round1Packages []*NativeFROSTDKGRound1Package, - ) (*NativeFROSTDKGPart2Result, error) - Part3( - secretPackage *NativeFROSTDKGRound2SecretPackage, - round1Packages []*NativeFROSTDKGRound1Package, - round2Packages []*NativeFROSTDKGRound2Package, - ) (*NativeFROSTDKGResult, error) -} - -type uniFFINativeFROSTDKGEngine struct { - bridge uniFFINativeFROSTDKGBridge -} - -func newUniFFINativeFROSTDKGEngine( - bridge uniFFINativeFROSTDKGBridge, -) (NativeFROSTDKGEngine, error) { - if bridge == nil { - return nil, fmt.Errorf("uniffi native FROST DKG bridge is nil") - } - - return &uniFFINativeFROSTDKGEngine{ - bridge: bridge, - }, nil -} - -func (unfdkg *uniFFINativeFROSTDKGEngine) Part1( - participantIdentifier string, - maxSigners uint16, - minSigners uint16, -) (*NativeFROSTDKGPart1Result, error) { - if participantIdentifier == "" { - return nil, fmt.Errorf("participant identifier is empty") - } - if maxSigners == 0 { - return nil, fmt.Errorf("max signers is zero") - } - if minSigners == 0 { - return nil, fmt.Errorf("min signers is zero") - } - if minSigners > maxSigners { - return nil, fmt.Errorf("min signers exceeds max signers") - } - - result, err := unfdkg.bridge.Part1( - participantIdentifier, - maxSigners, - minSigners, - ) - if err != nil { - return nil, err - } - - if err := validateNativeFROSTDKGPart1Result(result); err != nil { - return nil, err - } - - return result, nil -} - -func (unfdkg *uniFFINativeFROSTDKGEngine) Part2( - secretPackage *NativeFROSTDKGRound1SecretPackage, - round1Packages []*NativeFROSTDKGRound1Package, -) (*NativeFROSTDKGPart2Result, error) { - if secretPackage == nil { - return nil, fmt.Errorf("round-one secret package is nil") - } - if len(secretPackage.Data) == 0 { - return nil, fmt.Errorf("round-one secret package data is empty") - } - if len(round1Packages) == 0 { - return nil, fmt.Errorf("round-one packages are empty") - } - for i, pkg := range round1Packages { - if pkg == nil { - return nil, fmt.Errorf("round-one package [%d] is nil", i) - } - if pkg.Identifier == "" { - return nil, fmt.Errorf("round-one package [%d] identifier is empty", i) - } - if len(pkg.Data) == 0 { - return nil, fmt.Errorf("round-one package [%d] data is empty", i) - } - } - - result, err := unfdkg.bridge.Part2(secretPackage, round1Packages) - if err != nil { - return nil, err - } - - if err := validateNativeFROSTDKGPart2Result(result); err != nil { - return nil, err - } - - return result, nil -} - -func (unfdkg *uniFFINativeFROSTDKGEngine) Part3( - secretPackage *NativeFROSTDKGRound2SecretPackage, - round1Packages []*NativeFROSTDKGRound1Package, - round2Packages []*NativeFROSTDKGRound2Package, -) (*NativeFROSTDKGResult, error) { - if secretPackage == nil { - return nil, fmt.Errorf("round-two secret package is nil") - } - if len(secretPackage.Data) == 0 { - return nil, fmt.Errorf("round-two secret package data is empty") - } - if len(round1Packages) == 0 { - return nil, fmt.Errorf("round-one packages are empty") - } - if len(round2Packages) == 0 { - return nil, fmt.Errorf("round-two packages are empty") - } - for i, pkg := range round2Packages { - if pkg == nil { - return nil, fmt.Errorf("round-two package [%d] is nil", i) - } - if pkg.Identifier == "" { - return nil, fmt.Errorf("round-two package [%d] identifier is empty", i) - } - if pkg.SenderIdentifier == "" { - return nil, fmt.Errorf("round-two package [%d] sender identifier is empty", i) - } - if len(pkg.Data) == 0 { - return nil, fmt.Errorf("round-two package [%d] data is empty", i) - } - } - - result, err := unfdkg.bridge.Part3( - secretPackage, - round1Packages, - round2Packages, - ) - if err != nil { - return nil, err - } - - if err := validateNativeFROSTDKGResult(result); err != nil { - return nil, err - } - - return result, nil -} diff --git a/pkg/frost/signing/native_frost_dkg_protocol_frost_native.go b/pkg/frost/signing/native_frost_dkg_protocol_frost_native.go index c50b139104..229b790b04 100644 --- a/pkg/frost/signing/native_frost_dkg_protocol_frost_native.go +++ b/pkg/frost/signing/native_frost_dkg_protocol_frost_native.go @@ -857,6 +857,21 @@ func validateNativeFROSTDKGResult(result *NativeFROSTDKGResult) error { return fmt.Errorf("native FROST DKG result is nil") } - _, err := result.SignerMaterial() - return err + if result.KeyPackage == nil { + return fmt.Errorf("native FROST DKG key package is nil") + } + if result.KeyPackage.Identifier == "" { + return fmt.Errorf("native FROST DKG key package identifier is empty") + } + if len(result.KeyPackage.Data) == 0 { + return fmt.Errorf("native FROST DKG key package data is empty") + } + if result.PublicKeyPackage == nil { + return fmt.Errorf("native FROST DKG public key package is nil") + } + if result.PublicKeyPackage.VerifyingKey == "" { + return fmt.Errorf("native FROST DKG public key package verifying key is empty") + } + + return nil } diff --git a/pkg/frost/signing/native_frost_engine_frost_native.go b/pkg/frost/signing/native_frost_engine_frost_native.go index 38e3553a58..8c089b3c63 100644 --- a/pkg/frost/signing/native_frost_engine_frost_native.go +++ b/pkg/frost/signing/native_frost_engine_frost_native.go @@ -8,8 +8,9 @@ import ( ) const ( - // NativeSignerMaterialFormatFrostUniFFIV2 carries fully-native signer - // material required to execute two-round FROST signing. + // NativeSignerMaterialFormatFrostUniFFIV2 is the unsupported generic UniFFI + // FROST signer-material envelope. It is kept as a string constant so stale + // local/test material can be identified and rejected explicitly. NativeSignerMaterialFormatFrostUniFFIV2 = "frost-uniffi-v2" ) @@ -54,6 +55,16 @@ type NativeFROSTSignatureShare struct { Data []byte `json:"data"` } +type nativeFROSTCommitment struct { + Identifier string + Data []byte +} + +type nativeFROSTSignatureShare struct { + Identifier string + Data []byte +} + func (nfn *NativeFROSTNonces) consumeData() ([]byte, error) { if nfn == nil { return nil, fmt.Errorf("nonces are nil") diff --git a/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native.go b/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native.go index ca3d6b49ca..e70e1b75a4 100644 --- a/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native.go +++ b/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native.go @@ -430,9 +430,11 @@ func registerBuildTaggedNativeFROSTSigningEngine() error { // Do not register the tbtc-signer bridge as the generic UniFFI-shaped // FROST DKG/signing engine. That path persists `frost-uniffi-v2` wallet - // material, which cannot produce Taproot-tweaked signatures required for - // Taproot deposit sweeps. New FROST wallets in this build must use the - // coarse `frost-tbtc-signer-v1` material path exclusively. + // material, which cannot produce Taproot-tweaked signatures. A wallet + // using that material can accept Taproot deposits that are effectively + // unsweepable, so this must fail before new FROST wallet material exists. + // New FROST wallets in this build must use the coarse + // `frost-tbtc-signer-v1` material path exclusively. return RegisterNativeTBTCSignerEngine(engine) } @@ -588,7 +590,7 @@ func (bttse *buildTaggedTBTCSignerEngine) GenerateNoncesAndCommitments( func (bttse *buildTaggedTBTCSignerEngine) NewSigningPackage( message []byte, - commitments []uniFFINativeFROSTCommitment, + commitments []nativeFROSTCommitment, ) (signingPackageData []byte, err error) { requestPayload, err := buildTaggedTBTCSignerNewSigningPackageRequestPayload( message, @@ -635,7 +637,7 @@ func (bttse *buildTaggedTBTCSignerEngine) Sign( func (bttse *buildTaggedTBTCSignerEngine) Aggregate( signingPackageData []byte, - signatureShares []uniFFINativeFROSTSignatureShare, + signatureShares []nativeFROSTSignatureShare, publicKeyPackage *NativeFROSTPublicKeyPackage, ) (signature []byte, err error) { requestPayload, err := buildTaggedTBTCSignerAggregateRequestPayload( @@ -1229,7 +1231,7 @@ func decodeBuildTaggedTBTCSignerGenerateNoncesResponse( func buildTaggedTBTCSignerNewSigningPackageRequestPayload( message []byte, - commitments []uniFFINativeFROSTCommitment, + commitments []nativeFROSTCommitment, ) ([]byte, error) { if len(commitments) == 0 { return nil, buildTaggedTBTCSignerOperationError( @@ -1359,7 +1361,7 @@ func decodeBuildTaggedTBTCSignerSignShareResponse( func buildTaggedTBTCSignerAggregateRequestPayload( signingPackageData []byte, - signatureShares []uniFFINativeFROSTSignatureShare, + signatureShares []nativeFROSTSignatureShare, publicKeyPackage *NativeFROSTPublicKeyPackage, ) ([]byte, error) { if len(signingPackageData) == 0 { diff --git a/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native_test.go b/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native_test.go index c353378088..c316611eae 100644 --- a/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native_test.go +++ b/pkg/frost/signing/native_frost_engine_tbtc_signer_registration_frost_native_test.go @@ -239,7 +239,7 @@ func TestBuildTaggedTBTCSignerInteractiveFROSTBridge_WithLinkedSigner(t *testing } signingParticipants := []byte{1, 2} - commitments := make([]uniFFINativeFROSTCommitment, 0, len(signingParticipants)) + commitments := make([]nativeFROSTCommitment, 0, len(signingParticipants)) noncesByParticipant := make(map[byte][]byte, len(signingParticipants)) for _, participantID := range signingParticipants { nonces, commitmentIdentifier, commitmentData, err := @@ -250,7 +250,7 @@ func TestBuildTaggedTBTCSignerInteractiveFROSTBridge_WithLinkedSigner(t *testing if err != nil { t.Fatalf("unexpected nonce generation error: [%v]", err) } - commitments = append(commitments, uniFFINativeFROSTCommitment{ + commitments = append(commitments, nativeFROSTCommitment{ Identifier: commitmentIdentifier, Data: commitmentData, }) @@ -264,7 +264,7 @@ func TestBuildTaggedTBTCSignerInteractiveFROSTBridge_WithLinkedSigner(t *testing } signatureShares := make( - []uniFFINativeFROSTSignatureShare, + []nativeFROSTSignatureShare, 0, len(signingParticipants), ) @@ -278,7 +278,7 @@ func TestBuildTaggedTBTCSignerInteractiveFROSTBridge_WithLinkedSigner(t *testing if err != nil { t.Fatalf("unexpected signature share error: [%v]", err) } - signatureShares = append(signatureShares, uniFFINativeFROSTSignatureShare{ + signatureShares = append(signatureShares, nativeFROSTSignatureShare{ Identifier: signatureShareIdentifier, Data: signatureShareData, }) diff --git a/pkg/frost/signing/native_frost_engine_uniffi_frost_native.go b/pkg/frost/signing/native_frost_engine_uniffi_frost_native.go deleted file mode 100644 index 965f17d79c..0000000000 --- a/pkg/frost/signing/native_frost_engine_uniffi_frost_native.go +++ /dev/null @@ -1,228 +0,0 @@ -//go:build frost_native - -package signing - -import "fmt" - -type uniFFINativeFROSTCommitment struct { - Identifier string - Data []byte -} - -type uniFFINativeFROSTSignatureShare struct { - Identifier string - Data []byte -} - -type uniFFINativeFROSTBridge interface { - GenerateNoncesAndCommitments( - keyPackageIdentifier string, - keyPackageData []byte, - ) (noncesData []byte, commitmentIdentifier string, commitmentData []byte, err error) - NewSigningPackage( - message []byte, - commitments []uniFFINativeFROSTCommitment, - ) (signingPackageData []byte, err error) - Sign( - signingPackageData []byte, - noncesData []byte, - keyPackageIdentifier string, - keyPackageData []byte, - ) (signatureShareIdentifier string, signatureShareData []byte, err error) - Aggregate( - signingPackageData []byte, - signatureShares []uniFFINativeFROSTSignatureShare, - publicKeyPackage *NativeFROSTPublicKeyPackage, - ) (signature []byte, err error) -} - -type uniFFINativeFROSTSigningEngine struct { - bridge uniFFINativeFROSTBridge -} - -func newUniFFINativeFROSTSigningEngine( - bridge uniFFINativeFROSTBridge, -) (NativeFROSTSigningEngine, error) { - if bridge == nil { - return nil, fmt.Errorf("uniffi native FROST bridge is nil") - } - - return &uniFFINativeFROSTSigningEngine{ - bridge: bridge, - }, nil -} - -func (unfse *uniFFINativeFROSTSigningEngine) GenerateNoncesAndCommitments( - keyPackage *NativeFROSTKeyPackage, -) (*NativeFROSTNonces, *NativeFROSTCommitment, error) { - if keyPackage == nil { - return nil, nil, fmt.Errorf("key package is nil") - } - - if keyPackage.Identifier == "" { - return nil, nil, fmt.Errorf("key package identifier is empty") - } - - if len(keyPackage.Data) == 0 { - return nil, nil, fmt.Errorf("key package data is empty") - } - - noncesData, commitmentIdentifier, commitmentData, err := unfse.bridge.GenerateNoncesAndCommitments( - keyPackage.Identifier, - append([]byte{}, keyPackage.Data...), - ) - if err != nil { - return nil, nil, err - } - - return &NativeFROSTNonces{ - Data: append([]byte{}, noncesData...), - }, &NativeFROSTCommitment{ - Identifier: commitmentIdentifier, - Data: append([]byte{}, commitmentData...), - }, nil -} - -func (unfse *uniFFINativeFROSTSigningEngine) NewSigningPackage( - message []byte, - commitments []*NativeFROSTCommitment, -) (*NativeFROSTSigningPackage, error) { - if len(commitments) == 0 { - return nil, fmt.Errorf("commitments are empty") - } - - bridgeCommitments := make([]uniFFINativeFROSTCommitment, 0, len(commitments)) - for i, commitment := range commitments { - if commitment == nil { - return nil, fmt.Errorf("commitment [%d] is nil", i) - } - - if commitment.Identifier == "" { - return nil, fmt.Errorf("commitment [%d] identifier is empty", i) - } - - if len(commitment.Data) == 0 { - return nil, fmt.Errorf("commitment [%d] data is empty", i) - } - - bridgeCommitments = append(bridgeCommitments, uniFFINativeFROSTCommitment{ - Identifier: commitment.Identifier, - Data: append([]byte{}, commitment.Data...), - }) - } - - signingPackageData, err := unfse.bridge.NewSigningPackage( - append([]byte{}, message...), - bridgeCommitments, - ) - if err != nil { - return nil, err - } - - return &NativeFROSTSigningPackage{ - Data: append([]byte{}, signingPackageData...), - }, nil -} - -func (unfse *uniFFINativeFROSTSigningEngine) Sign( - signingPackage *NativeFROSTSigningPackage, - nonces *NativeFROSTNonces, - keyPackage *NativeFROSTKeyPackage, -) (*NativeFROSTSignatureShare, error) { - if signingPackage == nil { - return nil, fmt.Errorf("signing package is nil") - } - - if len(signingPackage.Data) == 0 { - return nil, fmt.Errorf("signing package data is empty") - } - - if keyPackage == nil { - return nil, fmt.Errorf("key package is nil") - } - - if keyPackage.Identifier == "" { - return nil, fmt.Errorf("key package identifier is empty") - } - - if len(keyPackage.Data) == 0 { - return nil, fmt.Errorf("key package data is empty") - } - - noncesData, err := nonces.consumeData() - if err != nil { - return nil, err - } - defer zeroBytes(noncesData) - - identifier, signatureShareData, err := unfse.bridge.Sign( - append([]byte{}, signingPackage.Data...), - noncesData, - keyPackage.Identifier, - append([]byte{}, keyPackage.Data...), - ) - if err != nil { - return nil, err - } - - return &NativeFROSTSignatureShare{ - Identifier: identifier, - Data: append([]byte{}, signatureShareData...), - }, nil -} - -func (unfse *uniFFINativeFROSTSigningEngine) Aggregate( - signingPackage *NativeFROSTSigningPackage, - signatureShares []*NativeFROSTSignatureShare, - publicKeyPackage *NativeFROSTPublicKeyPackage, -) ([]byte, error) { - if signingPackage == nil { - return nil, fmt.Errorf("signing package is nil") - } - - if len(signingPackage.Data) == 0 { - return nil, fmt.Errorf("signing package data is empty") - } - - if len(signatureShares) == 0 { - return nil, fmt.Errorf("signature shares are empty") - } - - if publicKeyPackage == nil { - return nil, fmt.Errorf("public key package is nil") - } - - bridgeSignatureShares := make([]uniFFINativeFROSTSignatureShare, 0, len(signatureShares)) - for i, signatureShare := range signatureShares { - if signatureShare == nil { - return nil, fmt.Errorf("signature share [%d] is nil", i) - } - - if signatureShare.Identifier == "" { - return nil, fmt.Errorf("signature share [%d] identifier is empty", i) - } - - if len(signatureShare.Data) == 0 { - return nil, fmt.Errorf("signature share [%d] data is empty", i) - } - - bridgeSignatureShares = append( - bridgeSignatureShares, - uniFFINativeFROSTSignatureShare{ - Identifier: signatureShare.Identifier, - Data: append([]byte{}, signatureShare.Data...), - }, - ) - } - - signature, err := unfse.bridge.Aggregate( - append([]byte{}, signingPackage.Data...), - bridgeSignatureShares, - publicKeyPackage, - ) - if err != nil { - return nil, err - } - - return append([]byte{}, signature...), nil -} diff --git a/pkg/frost/signing/native_frost_engine_uniffi_frost_native_test.go b/pkg/frost/signing/native_frost_engine_uniffi_frost_native_test.go deleted file mode 100644 index 9f151d38a0..0000000000 --- a/pkg/frost/signing/native_frost_engine_uniffi_frost_native_test.go +++ /dev/null @@ -1,289 +0,0 @@ -//go:build frost_native - -package signing - -import ( - "bytes" - "errors" - "testing" -) - -type mockUniFFINativeFROSTBridge struct { - generateNoncesAndCommitmentsFn func( - keyPackageIdentifier string, - keyPackageData []byte, - ) ([]byte, string, []byte, error) - newSigningPackageFn func( - message []byte, - commitments []uniFFINativeFROSTCommitment, - ) ([]byte, error) - signFn func( - signingPackageData []byte, - noncesData []byte, - keyPackageIdentifier string, - keyPackageData []byte, - ) (string, []byte, error) - aggregateFn func( - signingPackageData []byte, - signatureShares []uniFFINativeFROSTSignatureShare, - publicKeyPackage *NativeFROSTPublicKeyPackage, - ) ([]byte, error) -} - -func (munfsb *mockUniFFINativeFROSTBridge) GenerateNoncesAndCommitments( - keyPackageIdentifier string, - keyPackageData []byte, -) ([]byte, string, []byte, error) { - return munfsb.generateNoncesAndCommitmentsFn( - keyPackageIdentifier, - keyPackageData, - ) -} - -func (munfsb *mockUniFFINativeFROSTBridge) NewSigningPackage( - message []byte, - commitments []uniFFINativeFROSTCommitment, -) ([]byte, error) { - return munfsb.newSigningPackageFn(message, commitments) -} - -func (munfsb *mockUniFFINativeFROSTBridge) Sign( - signingPackageData []byte, - noncesData []byte, - keyPackageIdentifier string, - keyPackageData []byte, -) (string, []byte, error) { - return munfsb.signFn( - signingPackageData, - noncesData, - keyPackageIdentifier, - keyPackageData, - ) -} - -func (munfsb *mockUniFFINativeFROSTBridge) Aggregate( - signingPackageData []byte, - signatureShares []uniFFINativeFROSTSignatureShare, - publicKeyPackage *NativeFROSTPublicKeyPackage, -) ([]byte, error) { - return munfsb.aggregateFn(signingPackageData, signatureShares, publicKeyPackage) -} - -func TestNewUniFFINativeFROSTSigningEngine_NilBridge(t *testing.T) { - _, err := newUniFFINativeFROSTSigningEngine(nil) - if err == nil { - t.Fatal("expected error") - } -} - -func TestUniFFINativeFROSTSigningEngine_GenerateNoncesAndCommitments(t *testing.T) { - var capturedIdentifier string - var capturedData []byte - - engine, err := newUniFFINativeFROSTSigningEngine(&mockUniFFINativeFROSTBridge{ - generateNoncesAndCommitmentsFn: func( - keyPackageIdentifier string, - keyPackageData []byte, - ) ([]byte, string, []byte, error) { - capturedIdentifier = keyPackageIdentifier - capturedData = append([]byte{}, keyPackageData...) - return []byte{0x01, 0x02}, "id-1", []byte{0x03, 0x04}, nil - }, - }) - if err != nil { - t.Fatalf("unexpected constructor error: [%v]", err) - } - - nonces, commitment, err := engine.GenerateNoncesAndCommitments( - &NativeFROSTKeyPackage{ - Identifier: "member-1", - Data: []byte{0xaa, 0xbb}, - }, - ) - if err != nil { - t.Fatalf("unexpected generation error: [%v]", err) - } - - if capturedIdentifier != "member-1" { - t.Fatalf( - "unexpected key package identifier\nexpected: [%v]\nactual: [%v]", - "member-1", - capturedIdentifier, - ) - } - - if !bytes.Equal(capturedData, []byte{0xaa, 0xbb}) { - t.Fatalf( - "unexpected key package data\nexpected: [%x]\nactual: [%x]", - []byte{0xaa, 0xbb}, - capturedData, - ) - } - - if !bytes.Equal(nonces.Data, []byte{0x01, 0x02}) { - t.Fatalf( - "unexpected nonces data\nexpected: [%x]\nactual: [%x]", - []byte{0x01, 0x02}, - nonces.Data, - ) - } - - if commitment.Identifier != "id-1" { - t.Fatalf( - "unexpected commitment identifier\nexpected: [%v]\nactual: [%v]", - "id-1", - commitment.Identifier, - ) - } - - if !bytes.Equal(commitment.Data, []byte{0x03, 0x04}) { - t.Fatalf( - "unexpected commitment data\nexpected: [%x]\nactual: [%x]", - []byte{0x03, 0x04}, - commitment.Data, - ) - } -} - -func TestUniFFINativeFROSTSigningEngine_SignAndAggregate(t *testing.T) { - expectedErr := errors.New("aggregate error") - var signCalls int - var capturedNonces []byte - - engine, err := newUniFFINativeFROSTSigningEngine(&mockUniFFINativeFROSTBridge{ - generateNoncesAndCommitmentsFn: func( - keyPackageIdentifier string, - keyPackageData []byte, - ) ([]byte, string, []byte, error) { - return nil, "", nil, nil - }, - newSigningPackageFn: func( - message []byte, - commitments []uniFFINativeFROSTCommitment, - ) ([]byte, error) { - return []byte{0x01}, nil - }, - signFn: func( - signingPackageData []byte, - noncesData []byte, - keyPackageIdentifier string, - keyPackageData []byte, - ) (string, []byte, error) { - signCalls++ - capturedNonces = append([]byte{}, noncesData...) - return "member-1", []byte{0x99}, nil - }, - aggregateFn: func( - signingPackageData []byte, - signatureShares []uniFFINativeFROSTSignatureShare, - publicKeyPackage *NativeFROSTPublicKeyPackage, - ) ([]byte, error) { - return nil, expectedErr - }, - }) - if err != nil { - t.Fatalf("unexpected constructor error: [%v]", err) - } - - signingPackage, err := engine.NewSigningPackage( - []byte{0xab}, - []*NativeFROSTCommitment{ - { - Identifier: "member-1", - Data: []byte{0x11}, - }, - }, - ) - if err != nil { - t.Fatalf("unexpected signing package error: [%v]", err) - } - - nonceBacking := []byte{0x22} - nonces := &NativeFROSTNonces{ - Data: nonceBacking, - } - signatureShare, err := engine.Sign( - signingPackage, - nonces, - &NativeFROSTKeyPackage{ - Identifier: "member-1", - Data: []byte{0x33}, - }, - ) - if err != nil { - t.Fatalf("unexpected sign error: [%v]", err) - } - - if signatureShare.Identifier != "member-1" { - t.Fatalf( - "unexpected signature share identifier\nexpected: [%v]\nactual: [%v]", - "member-1", - signatureShare.Identifier, - ) - } - - if !bytes.Equal(signatureShare.Data, []byte{0x99}) { - t.Fatalf( - "unexpected signature share data\nexpected: [%x]\nactual: [%x]", - []byte{0x99}, - signatureShare.Data, - ) - } - if signCalls != 1 { - t.Fatalf("unexpected sign call count: [%d]", signCalls) - } - if !bytes.Equal(capturedNonces, []byte{0x22}) { - t.Fatalf( - "unexpected bridge nonces\nexpected: [%x]\nactual: [%x]", - []byte{0x22}, - capturedNonces, - ) - } - if len(nonces.Data) != 0 { - t.Fatalf("expected consumed nonce data to be cleared: [%x]", nonces.Data) - } - if !bytes.Equal(nonceBacking, []byte{0x00}) { - t.Fatalf( - "expected nonce backing array to be wiped\nactual: [%x]", - nonceBacking, - ) - } - - _, err = engine.Sign( - signingPackage, - nonces, - &NativeFROSTKeyPackage{ - Identifier: "member-1", - Data: []byte{0x33}, - }, - ) - if err == nil { - t.Fatal("expected consumed nonce reuse error") - } - if err.Error() != "nonces are already consumed" { - t.Fatalf("unexpected consumed nonce error: [%v]", err) - } - if signCalls != 1 { - t.Fatalf("consumed nonce reuse reached bridge; sign calls: [%d]", signCalls) - } - - _, err = engine.Aggregate( - signingPackage, - []*NativeFROSTSignatureShare{ - signatureShare, - }, - &NativeFROSTPublicKeyPackage{ - VerifyingShares: map[string]string{ - "member-1": "share-1", - }, - VerifyingKey: "pubkey", - }, - ) - if !errors.Is(err, expectedErr) { - t.Fatalf( - "unexpected aggregate error\nexpected: [%v]\nactual: [%v]", - expectedErr, - err, - ) - } -} diff --git a/pkg/frost/signing/native_frost_protocol_frost_native_test.go b/pkg/frost/signing/native_frost_protocol_frost_native_test.go index 635595cff7..2a3893f1f9 100644 --- a/pkg/frost/signing/native_frost_protocol_frost_native_test.go +++ b/pkg/frost/signing/native_frost_protocol_frost_native_test.go @@ -13,7 +13,6 @@ import ( "strings" "sync" "testing" - "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -242,14 +241,11 @@ func (rnfse *recordingNativeFROSTSigningEngine) signatureShareIDs() [][]string { return snapshots } -func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_NativeFROSTPath( +func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_UnsupportedNativeFROSTPath( t *testing.T, ) { - RegisterNativeFROSTSigningEngine(&deterministicNativeFROSTSigningEngine{}) - t.Cleanup(UnregisterNativeFROSTSigningEngine) - provider := local.Connect() - channel, err := provider.BroadcastChannelFor("native-frost-signing-protocol-test") + channel, err := provider.BroadcastChannelFor("unsupported-native-frost-signing-test") if err != nil { t.Fatalf("failed creating broadcast channel: [%v]", err) } @@ -257,77 +253,23 @@ func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_Nati primitive := &buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive{} primitive.RegisterUnmarshallers(channel) - participantCount := 3 - includedMembers := []group.MemberIndex{1, 2, 3} - - requests := make([]*NativeExecutionFFISigningRequest, participantCount) - for i := 0; i < participantCount; i++ { - memberIndex := group.MemberIndex(i + 1) - requests[i], err = newNativeFROSTSigningRequestForTest( - memberIndex, - includedMembers, - channel, - participantCount, - ) - if err != nil { - t.Fatalf("failed preparing request for member [%v]: [%v]", memberIndex, err) - } - } - - ctx, cancelCtx := context.WithTimeout(context.Background(), 10*time.Second) - defer cancelCtx() - - results := make([]*frostSignatureResultForTest, participantCount) - wg := sync.WaitGroup{} - wg.Add(participantCount) - - for i := 0; i < participantCount; i++ { - go func(index int) { - defer wg.Done() - - signature, signErr := primitive.Sign(ctx, nil, requests[index]) - results[index] = &frostSignatureResultForTest{ - signature: signature, - err: signErr, - } - }(i) + request, err := newNativeFROSTSigningRequestForTest( + 1, + []group.MemberIndex{1}, + channel, + 1, + ) + if err != nil { + t.Fatalf("failed creating native request: [%v]", err) } - wg.Wait() - - for i, result := range results { - if result == nil { - t.Fatalf("missing result for member [%v]", i+1) - } - - if result.err != nil { - t.Fatalf( - "unexpected signing error for member [%v]: [%v]", - i+1, - result.err, - ) - } - - if result.signature == nil { - t.Fatalf("nil signature for member [%v]", i+1) - } + _, err = primitive.Sign(context.Background(), nil, request) + if !errors.Is(err, ErrUnsupportedSignerMaterialFormat) { + t.Fatalf("expected unsupported material error, got [%v]", err) } - - for i := 1; i < participantCount; i++ { - if !results[0].signature.Equals(results[i].signature) { - t.Fatalf( - "signature mismatch\nfirst: [%v]\nsecond: [%v]", - results[0].signature, - results[i].signature, - ) - } + if !strings.Contains(err.Error(), "unsupported") { + t.Fatalf("error should mention unsupported format: [%v]", err) } - - assertNativeFROSTSignatureVerifiesBIP340( - t, - results[0].signature, - requests[0], - ) } func TestVerifyNativeFROSTBIP340SignatureRejectsInvalidAggregate( @@ -351,191 +293,7 @@ func TestVerifyNativeFROSTBIP340SignatureRejectsInvalidAggregate( } } -func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_NativeFROSTPath_AttemptVariationUsesCohortSelections( - t *testing.T, -) { - engine := &recordingNativeFROSTSigningEngine{} - RegisterNativeFROSTSigningEngine(engine) - t.Cleanup(UnregisterNativeFROSTSigningEngine) - - provider := local.Connect() - channel, err := provider.BroadcastChannelFor("native-frost-signing-protocol-attempt-variation-test") - if err != nil { - t.Fatalf("failed creating broadcast channel: [%v]", err) - } - - primitive := &buildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive{} - primitive.RegisterUnmarshallers(channel) - - runRound := func( - sessionID string, - includedMembers []group.MemberIndex, - groupSize int, - ) []*frost.Signature { - requests := make([]*NativeExecutionFFISigningRequest, len(includedMembers)) - for i := 0; i < len(includedMembers); i++ { - memberIndex := includedMembers[i] - - request, roundErr := newNativeFROSTSigningRequestWithSessionForTest( - memberIndex, - includedMembers, - channel, - groupSize, - sessionID, - ) - if roundErr != nil { - t.Fatalf( - "failed preparing request for member [%v] in session [%s]: [%v]", - memberIndex, - sessionID, - roundErr, - ) - } - - requests[i] = request - } - - ctx, cancelCtx := context.WithTimeout(context.Background(), 10*time.Second) - defer cancelCtx() - - results := make([]*frostSignatureResultForTest, len(includedMembers)) - var wg sync.WaitGroup - wg.Add(len(includedMembers)) - - for i := 0; i < len(includedMembers); i++ { - go func(index int) { - defer wg.Done() - - signature, signErr := primitive.Sign(ctx, nil, requests[index]) - results[index] = &frostSignatureResultForTest{ - signature: signature, - err: signErr, - } - }(i) - } - - wg.Wait() - - signatures := make([]*frost.Signature, len(includedMembers)) - for i := 0; i < len(includedMembers); i++ { - if results[i] == nil { - t.Fatalf( - "missing signing result for member [%v] in session [%s]", - includedMembers[i], - sessionID, - ) - } - - if results[i].err != nil { - t.Fatalf( - "unexpected signing error for member [%v] in session [%s]: [%v]", - includedMembers[i], - sessionID, - results[i].err, - ) - } - - if results[i].signature == nil { - t.Fatalf( - "nil signature for member [%v] in session [%s]", - includedMembers[i], - sessionID, - ) - } - - signatures[i] = results[i].signature - } - - return signatures - } - - assertSignaturesMatch := func( - sessionID string, - signatures []*frost.Signature, - ) { - if len(signatures) == 0 { - t.Fatalf("no signatures for session [%s]", sessionID) - } - - for i := 1; i < len(signatures); i++ { - if !signatures[0].Equals(signatures[i]) { - t.Fatalf( - "signature mismatch in session [%s]\nfirst: [%v]\nsecond: [%v]", - sessionID, - signatures[0], - signatures[i], - ) - } - } - } - - roundOneSignatures := runRound( - "native-frost-signing-session-attempt-1", - []group.MemberIndex{1, 2, 3}, - 3, - ) - assertSignaturesMatch("native-frost-signing-session-attempt-1", roundOneSignatures) - - roundTwoSignatures := runRound( - "native-frost-signing-session-attempt-2", - []group.MemberIndex{1, 3}, - 3, - ) - assertSignaturesMatch("native-frost-signing-session-attempt-2", roundTwoSignatures) - - snapshotHistogram := func(snapshots [][]string) map[string]int { - histogram := make(map[string]int) - for _, snapshot := range snapshots { - histogram[strings.Join(snapshot, ",")]++ - } - - return histogram - } - - expectedHistogram := map[string]int{ - "member-1,member-2,member-3": 3, - "member-1,member-3": 2, - } - - assertHistogram := func(name string, actual map[string]int) { - if len(actual) != len(expectedHistogram) { - t.Fatalf( - "unexpected %s histogram size\nexpected: [%v]\nactual: [%v]", - name, - len(expectedHistogram), - len(actual), - ) - } - - for key, expectedCount := range expectedHistogram { - actualCount, ok := actual[key] - if !ok { - t.Fatalf("missing %s histogram key: [%s]", name, key) - } - - if actualCount != expectedCount { - t.Fatalf( - "unexpected %s count for key [%s]\nexpected: [%v]\nactual: [%v]", - name, - key, - expectedCount, - actualCount, - ) - } - } - } - - assertHistogram( - "commitment IDs", - snapshotHistogram(engine.commitmentIDs()), - ) - assertHistogram( - "signature share IDs", - snapshotHistogram(engine.signatureShareIDs()), - ) -} - -func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_NativeFROSTPathWithoutEngine( +func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_UnsupportedNativeFROSTPathWithoutEngine( t *testing.T, ) { UnregisterNativeFROSTSigningEngine() @@ -565,13 +323,16 @@ func TestBuildTaggedLegacyCompatibleNativeExecutionFFISigningPrimitive_Sign_Nati t.Fatal("expected error") } - if !errors.Is(err, ErrNativeCryptographyUnavailable) { + if !errors.Is(err, ErrUnsupportedSignerMaterialFormat) { t.Fatalf( "unexpected error\nexpected: [%v]\nactual: [%v]", - ErrNativeCryptographyUnavailable, + ErrUnsupportedSignerMaterialFormat, err, ) } + if !strings.Contains(err.Error(), "unsupported") { + t.Fatalf("error should mention unsupported format: [%v]", err) + } } type frostSignatureResultForTest struct { diff --git a/pkg/frost/signing/roast_retry_executor_entry_frost_native_test.go b/pkg/frost/signing/roast_retry_executor_entry_frost_native_test.go index aed63494c1..e96c95077a 100644 --- a/pkg/frost/signing/roast_retry_executor_entry_frost_native_test.go +++ b/pkg/frost/signing/roast_retry_executor_entry_frost_native_test.go @@ -16,22 +16,15 @@ import ( func newEntryTestRequest(t *testing.T) *NativeExecutionFFISigningRequest { t.Helper() - const hexKey = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" - payload, _ := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ - KeyPackage: &NativeFROSTKeyPackage{ - Identifier: "id", - Data: []byte{0x01}, - }, - PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingKey: hexKey, - }, + payload, _ := json.Marshal(&NativeTBTCSignerMaterialPayload{ + KeyGroup: "tbtc-signer-entry-group", }) return &NativeExecutionFFISigningRequest{ Message: new(big.Int).SetBytes([]byte{0xab, 0xcd}), SessionID: "executor-entry-test", MemberIndex: 1, SignerMaterial: &NativeSignerMaterial{ - Format: NativeSignerMaterialFormatFrostUniFFIV2, + Format: NativeSignerMaterialFormatFrostTBTCSignerV1, Payload: payload, }, Attempt: &Attempt{ @@ -80,7 +73,7 @@ func TestEntry_LogsSignerMaterialFormatTelemetry(t *testing.T) { joined := strings.Join(logger.infoMessages, "\n") if !strings.Contains(joined, "signer_material_format") || - !strings.Contains(joined, NativeSignerMaterialFormatFrostUniFFIV2) || + !strings.Contains(joined, NativeSignerMaterialFormatFrostTBTCSignerV1) || !strings.Contains(joined, "key_group_id") { t.Fatalf("missing signer-material telemetry in logs: [%s]", joined) } diff --git a/pkg/frost/signing/roast_retry_executor_entry_frost_roast_retry_test.go b/pkg/frost/signing/roast_retry_executor_entry_frost_roast_retry_test.go index 6329394de6..bfb5d76631 100644 --- a/pkg/frost/signing/roast_retry_executor_entry_frost_roast_retry_test.go +++ b/pkg/frost/signing/roast_retry_executor_entry_frost_roast_retry_test.go @@ -16,22 +16,15 @@ import ( func newEntryRetryTestRequest(t *testing.T) *NativeExecutionFFISigningRequest { t.Helper() - const hexKey = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" - payload, _ := json.Marshal(&nativeFROSTUniFFIV2SignerMaterial{ - KeyPackage: &NativeFROSTKeyPackage{ - Identifier: "id", - Data: []byte{0x01}, - }, - PublicKeyPackage: &NativeFROSTPublicKeyPackage{ - VerifyingKey: hexKey, - }, + payload, _ := json.Marshal(&NativeTBTCSignerMaterialPayload{ + KeyGroup: "tbtc-signer-entry-retry-group", }) return &NativeExecutionFFISigningRequest{ Message: new(big.Int).SetBytes([]byte{0xab, 0xcd}), SessionID: "executor-entry-retry-test", MemberIndex: 1, SignerMaterial: &NativeSignerMaterial{ - Format: NativeSignerMaterialFormatFrostUniFFIV2, + Format: NativeSignerMaterialFormatFrostTBTCSignerV1, Payload: payload, }, Attempt: &Attempt{ diff --git a/pkg/tbtc/frost_dkg_execution_frost_native.go b/pkg/tbtc/frost_dkg_execution_frost_native.go index 6c281bea3c..de4f984409 100644 --- a/pkg/tbtc/frost_dkg_execution_frost_native.go +++ b/pkg/tbtc/frost_dkg_execution_frost_native.go @@ -13,7 +13,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "go.uber.org/zap" - "github.com/ipfs/go-log/v2" "github.com/keep-network/keep-core/pkg/frost" "github.com/keep-network/keep-core/pkg/frost/registry" frostsigning "github.com/keep-network/keep-core/pkg/frost/signing" @@ -33,12 +32,11 @@ func executeFrostDKGIfPossible( memberIndexes []group.MemberIndex, groupSelectionResult *GroupSelectionResult, ) { - nativeFROSTDKGEngine := frostsigning.CurrentNativeFROSTDKGEngine() nativeTBTCSignerEngine := frostsigning.CurrentNativeTBTCSignerEngine() - if nativeFROSTDKGEngine == nil && nativeTBTCSignerEngine == nil { + if nativeTBTCSignerEngine == nil { logger.Infof( "FROST DKG with seed [0x%x] selected this operator as member "+ - "indexes [%v], but no native FROST DKG or tbtc-signer engine is registered", + "indexes [%v], but no native tbtc-signer engine is registered", event.Seed, memberIndexes, ) @@ -131,19 +129,22 @@ func executeFrostDKGIfPossible( return } + tbtcSignerMemberIndexes, err := finalFrostDKGMemberIndexes( + activeMemberIndexes, + groupSelectionResult, + node.groupParameters, + ) + if err != nil { + dkgLogger.Errorf("failed to resolve final FROST DKG member indexes: [%v]", err) + return + } + executionResult, err := executeFrostDKG( - dkgCtx, - dkgLogger, - nativeFROSTDKGEngine, nativeTBTCSignerEngine, event, - memberIndex, - activeMemberIndexes, - groupSelectionResult, + tbtcSignerMemberIndexes, signatureThreshold, sessionID, - channel, - membershipValidator, ) if err != nil { dkgLogger.Errorf("FROST DKG execution failed: [%v]", err) @@ -233,71 +234,72 @@ type frostDKGExecutionResult struct { } func executeFrostDKG( - ctx context.Context, - logger log.StandardLogger, - nativeFROSTDKGEngine frostsigning.NativeFROSTDKGEngine, nativeTBTCSignerEngine frostsigning.NativeTBTCSignerEngine, event *FrostDKGStartedEvent, - memberIndex group.MemberIndex, - activeMemberIndexes []group.MemberIndex, - groupSelectionResult *GroupSelectionResult, + dkgMemberIndexes []group.MemberIndex, signatureThreshold int, sessionID string, - channel net.BroadcastChannel, - membershipValidator *group.MembershipValidator, ) (*frostDKGExecutionResult, error) { if nativeTBTCSignerEngine != nil { return executeTBTCSignerFROSTDKG( nativeTBTCSignerEngine, event, - activeMemberIndexes, + dkgMemberIndexes, signatureThreshold, sessionID, ) } - if nativeFROSTDKGEngine != nil { - nativeResult, err := frostsigning.ExecuteNativeFROSTDKG( - ctx, - logger, - &frostsigning.NativeFROSTDKGRequest{ - MemberIndex: memberIndex, - GroupSize: len(groupSelectionResult.OperatorsIDs), - Threshold: signatureThreshold, - SessionID: sessionID, - IncludedMembersIndexes: activeMemberIndexes, - Channel: channel, - MembershipValidator: membershipValidator, - }, - nativeFROSTDKGEngine, - ) - if err != nil { - return nil, fmt.Errorf("native FROST DKG execution failed: [%w]", err) - } + return nil, fmt.Errorf("native tbtc-signer engine is unavailable") +} - signerMaterial, err := nativeResult.SignerMaterial() - if err != nil { - return nil, err - } +func finalFrostDKGMemberIndexes( + activeMemberIndexes []group.MemberIndex, + groupSelectionResult *GroupSelectionResult, + groupParameters *GroupParameters, +) ([]group.MemberIndex, error) { + if groupSelectionResult == nil { + return nil, fmt.Errorf("group selection result is nil") + } + if groupParameters == nil { + return nil, fmt.Errorf("group parameters are nil") + } + + operatingMembersIndexes := append([]group.MemberIndex{}, activeMemberIndexes...) + _, finalSigningGroupMembersIndexes, err := finalSigningGroup( + groupSelectionResult.OperatorsAddresses, + operatingMembersIndexes, + groupParameters, + ) + if err != nil { + return nil, err + } - outputKey, err := outputKeyFromNativeDKGResult(nativeResult) - if err != nil { - return nil, err + dkgMemberIndexes := make( + []group.MemberIndex, + 0, + len(operatingMembersIndexes), + ) + for _, activeMemberIndex := range operatingMembersIndexes { + finalMemberIndex, ok := + finalSigningGroupMembersIndexes[activeMemberIndex] + if !ok { + return nil, fmt.Errorf( + "active member [%d] is missing final FROST DKG member index", + activeMemberIndex, + ) } - return &frostDKGExecutionResult{ - outputKey: outputKey, - signerMaterial: signerMaterial, - }, nil + dkgMemberIndexes = append(dkgMemberIndexes, finalMemberIndex) } - return nil, fmt.Errorf("native FROST DKG engine is unavailable") + return dkgMemberIndexes, nil } func executeTBTCSignerFROSTDKG( nativeEngine frostsigning.NativeTBTCSignerEngine, event *FrostDKGStartedEvent, - activeMemberIndexes []group.MemberIndex, + dkgMemberIndexes []group.MemberIndex, signatureThreshold int, sessionID string, ) (*frostDKGExecutionResult, error) { @@ -315,7 +317,7 @@ func executeTBTCSignerFROSTDKG( return nil, err } - participants, err := nativeTBTCSignerDKGParticipants(activeMemberIndexes) + participants, err := nativeTBTCSignerDKGParticipants(dkgMemberIndexes) if err != nil { return nil, err } diff --git a/pkg/tbtc/frost_dkg_execution_frost_native_test.go b/pkg/tbtc/frost_dkg_execution_frost_native_test.go index e4b0e76a11..1afb0be72f 100644 --- a/pkg/tbtc/frost_dkg_execution_frost_native_test.go +++ b/pkg/tbtc/frost_dkg_execution_frost_native_test.go @@ -4,12 +4,14 @@ package tbtc import ( "bytes" - "context" "encoding/hex" + "encoding/json" "fmt" "math/big" + "strings" "testing" + "github.com/keep-network/keep-core/pkg/chain" "github.com/keep-network/keep-core/pkg/frost/registry" frostsigning "github.com/keep-network/keep-core/pkg/frost/signing" "github.com/keep-network/keep-core/pkg/protocol/group" @@ -103,23 +105,15 @@ func TestOutputKeyFromTBTCSignerDKGResult_AcceptsCompressedKeyGroup( } } -func TestExecuteFrostDKG_PrefersTBTCSignerMaterial(t *testing.T) { +func TestExecuteFrostDKG_UsesTBTCSignerMaterial(t *testing.T) { tbtcSignerEngine := &testNativeTBTCSignerSeededDKGEngine{} - uniffiEngine := &testNativeFROSTDKGEngine{} result, err := executeFrostDKG( - context.Background(), - nil, - uniffiEngine, tbtcSignerEngine, &FrostDKGStartedEvent{Seed: big.NewInt(0x1234)}, - 1, []group.MemberIndex{1, 2, 3}, - &GroupSelectionResult{}, 2, "test-session", - nil, - nil, ) if err != nil { t.Fatalf("unexpected DKG error: [%v]", err) @@ -128,12 +122,14 @@ func TestExecuteFrostDKG_PrefersTBTCSignerMaterial(t *testing.T) { if !tbtcSignerEngine.runDKGWithSeedCalled { t.Fatal("expected tbtc-signer DKG engine to be used") } - if uniffiEngine.called { - t.Fatal("did not expect UniFFI native FROST DKG engine to be used") - } if result.signerMaterial == nil { t.Fatal("expected signer material") } + assertTBTCSignerDKGParticipantIdentifiers( + t, + tbtcSignerEngine.runDKGWithSeedParticipants, + []uint16{1, 2, 3}, + ) if result.signerMaterial.Format != frostsigning.NativeSignerMaterialFormatFrostTBTCSignerV1 { t.Fatalf( @@ -142,10 +138,94 @@ func TestExecuteFrostDKG_PrefersTBTCSignerMaterial(t *testing.T) { result.signerMaterial.Format, ) } + + var payload frostsigning.NativeTBTCSignerMaterialPayload + if err := json.Unmarshal(result.signerMaterial.Payload, &payload); err != nil { + t.Fatalf("unexpected signer material payload decode error: [%v]", err) + } + assertTBTCSignerDKGParticipantIdentifiers( + t, + payload.DKGParticipants, + []uint16{1, 2, 3}, + ) +} + +func TestFinalFrostDKGMemberIndexes_NormalizesToFinalSigningGroupIndexes( + t *testing.T, +) { + activeMemberIndexes := []group.MemberIndex{5, 2, 4} + + actual, err := finalFrostDKGMemberIndexes( + activeMemberIndexes, + &GroupSelectionResult{ + OperatorsAddresses: chain.Addresses{ + "0xAA", + "0xBB", + "0xCC", + "0xDD", + "0xEE", + }, + }, + &GroupParameters{ + GroupSize: 5, + GroupQuorum: 3, + HonestThreshold: 2, + }, + ) + if err != nil { + t.Fatalf("unexpected final member index error: [%v]", err) + } + + expected := []group.MemberIndex{1, 2, 3} + if len(actual) != len(expected) { + t.Fatalf( + "unexpected final member indexes count\nexpected: [%d]\nactual: [%d]", + len(expected), + len(actual), + ) + } + for i := range expected { + if actual[i] != expected[i] { + t.Fatalf( + "unexpected final member index at [%d]\nexpected: [%d]\nactual: [%d]", + i, + expected[i], + actual[i], + ) + } + } + + expectedActive := []group.MemberIndex{5, 2, 4} + for i := range expectedActive { + if activeMemberIndexes[i] != expectedActive[i] { + t.Fatalf( + "active member indexes should not be mutated\nexpected: [%v]\nactual: [%v]", + expectedActive, + activeMemberIndexes, + ) + } + } +} + +func TestExecuteFrostDKG_RequiresTBTCSignerMaterial(t *testing.T) { + _, err := executeFrostDKG( + nil, + &FrostDKGStartedEvent{Seed: big.NewInt(0x1234)}, + []group.MemberIndex{1, 2, 3}, + 2, + "test-session", + ) + if err == nil { + t.Fatal("expected missing tbtc-signer engine error") + } + if !strings.Contains(err.Error(), "native tbtc-signer engine is unavailable") { + t.Fatalf("unexpected error: [%v]", err) + } } type testNativeTBTCSignerSeededDKGEngine struct { - runDKGWithSeedCalled bool + runDKGWithSeedCalled bool + runDKGWithSeedParticipants []frostsigning.NativeTBTCSignerDKGParticipant } func (tntsde *testNativeTBTCSignerSeededDKGEngine) RunDKG( @@ -163,6 +243,10 @@ func (tntsde *testNativeTBTCSignerSeededDKGEngine) RunDKGWithSeed( dkgSeedHex string, ) (*frostsigning.NativeTBTCSignerDKGResult, error) { tntsde.runDKGWithSeedCalled = true + tntsde.runDKGWithSeedParticipants = append( + []frostsigning.NativeTBTCSignerDKGParticipant{}, + participants..., + ) if sessionID != "test-session" { return nil, fmt.Errorf("unexpected session ID: [%s]", sessionID) @@ -214,32 +298,29 @@ func (tntsde *testNativeTBTCSignerSeededDKGEngine) BuildTaprootTx( return nil, fmt.Errorf("BuildTaprootTx should not be used") } -type testNativeFROSTDKGEngine struct { - called bool -} - -func (tnfdkg *testNativeFROSTDKGEngine) Part1( - string, - uint16, - uint16, -) (*frostsigning.NativeFROSTDKGPart1Result, error) { - tnfdkg.called = true - return nil, fmt.Errorf("UniFFI DKG Part1 should not be used") -} +func assertTBTCSignerDKGParticipantIdentifiers( + t *testing.T, + participants []frostsigning.NativeTBTCSignerDKGParticipant, + expected []uint16, +) { + t.Helper() -func (tnfdkg *testNativeFROSTDKGEngine) Part2( - *frostsigning.NativeFROSTDKGRound1SecretPackage, - []*frostsigning.NativeFROSTDKGRound1Package, -) (*frostsigning.NativeFROSTDKGPart2Result, error) { - tnfdkg.called = true - return nil, fmt.Errorf("UniFFI DKG Part2 should not be used") -} + if len(participants) != len(expected) { + t.Fatalf( + "unexpected participant count\nexpected: [%d]\nactual: [%d]", + len(expected), + len(participants), + ) + } -func (tnfdkg *testNativeFROSTDKGEngine) Part3( - *frostsigning.NativeFROSTDKGRound2SecretPackage, - []*frostsigning.NativeFROSTDKGRound1Package, - []*frostsigning.NativeFROSTDKGRound2Package, -) (*frostsigning.NativeFROSTDKGResult, error) { - tnfdkg.called = true - return nil, fmt.Errorf("UniFFI DKG Part3 should not be used") + for i := range expected { + if participants[i].Identifier != expected[i] { + t.Fatalf( + "unexpected participant identifier at [%d]\nexpected: [%d]\nactual: [%d]", + i, + expected[i], + participants[i].Identifier, + ) + } + } } diff --git a/pkg/tbtc/signing_native_backend_frost_native_test.go b/pkg/tbtc/signing_native_backend_frost_native_test.go index 89444fe244..df6afaadd9 100644 --- a/pkg/tbtc/signing_native_backend_frost_native_test.go +++ b/pkg/tbtc/signing_native_backend_frost_native_test.go @@ -90,7 +90,7 @@ func (dnefspf *deterministicNativeExecutionFFISigningPrimitiveForTBTC) Sign( return nil, fmt.Errorf("native signer material is nil") } - if nativeSignerMaterial.Format != frostsigning.NativeSignerMaterialFormatFrostUniFFIV2 { + if nativeSignerMaterial.Format != frostsigning.NativeSignerMaterialFormatFrostTBTCSignerV1 { return nil, fmt.Errorf( "unexpected signer material format: [%s]", nativeSignerMaterial.Format, @@ -459,7 +459,7 @@ func TestSigningExecutor_Sign_FFIStrictBackend_WithNativeSignerMaterial( t *testing.T, ) { executor := setupSigningExecutor(t) - configureSignersWithNativeFROSTUniFFIV2Material(t, executor) + configureSignersWithTBTCSignerMaterial(t, executor, 0) primitive := &deterministicNativeExecutionFFISigningPrimitiveForTBTC{} @@ -601,7 +601,7 @@ func TestSigningExecutor_Sign_FFIStrictBackend_AttemptVariationChangesCohortSele t *testing.T, ) { executor := setupSigningExecutor(t) - configureSignersWithNativeFROSTUniFFIV2Material(t, executor) + configureSignersWithTBTCSignerMaterial(t, executor, 0) primitive := &attemptTrackingNativeExecutionFFISigningPrimitiveForTBTC{} @@ -854,43 +854,6 @@ func TestSigningExecutor_Sign_FFIStrictBackend_TBTCSignerPath_AttemptVariationCh } } -func configureSignersWithNativeFROSTUniFFIV2Material( - t *testing.T, - executor *signingExecutor, -) { - t.Helper() - - publicKeyPackage := &frostsigning.NativeFROSTPublicKeyPackage{ - VerifyingShares: map[string]string{ - "1": "share-1", - }, - VerifyingKey: "group-verifying-key", - } - - for _, signer := range executor.signers { - keyPackage := &frostsigning.NativeFROSTKeyPackage{ - Identifier: strconv.FormatUint(uint64(signer.signingGroupMemberIndex), 10), - Data: []byte{byte(signer.signingGroupMemberIndex)}, - } - - payload, err := json.Marshal(struct { - KeyPackage *frostsigning.NativeFROSTKeyPackage `json:"keyPackage"` - PublicKeyPackage *frostsigning.NativeFROSTPublicKeyPackage `json:"publicKeyPackage"` - }{ - KeyPackage: keyPackage, - PublicKeyPackage: publicKeyPackage, - }) - if err != nil { - t.Fatalf("cannot marshal native signer material payload: [%v]", err) - } - - signer.signerMaterial = &frostsigning.NativeSignerMaterial{ - Format: frostsigning.NativeSignerMaterialFormatFrostUniFFIV2, - Payload: payload, - } - } -} - func configureSignersWithTBTCSignerMaterial( t *testing.T, executor *signingExecutor, diff --git a/pkg/tbtc/signing_schnorr_frost_native_test.go b/pkg/tbtc/signing_schnorr_frost_native_test.go index b6c88ea424..e411242963 100644 --- a/pkg/tbtc/signing_schnorr_frost_native_test.go +++ b/pkg/tbtc/signing_schnorr_frost_native_test.go @@ -50,7 +50,7 @@ func TestSigningMaterialUsesSchnorrSignatures_FrostNative(t *testing.T) { }, expectSchnorr: false, }, - "native frost material": { + "unsupported uniffi frost material": { material: &frostsigning.NativeSignerMaterial{ Format: frostsigning.NativeSignerMaterialFormatFrostUniFFIV2, Payload: []byte{0x01}, diff --git a/pkg/tbtc/wallet_id_from_signer_frost_native.go b/pkg/tbtc/wallet_id_from_signer_frost_native.go index 045168cc79..e19da61aa6 100644 --- a/pkg/tbtc/wallet_id_from_signer_frost_native.go +++ b/pkg/tbtc/wallet_id_from_signer_frost_native.go @@ -35,24 +35,31 @@ func frostWalletIDFromSigner(signer *signer) ([32]byte, bool, error) { return [32]byte{}, false, nil } - if material.Format != frostsigning.NativeSignerMaterialFormatFrostUniFFIV2 && - material.Format != frostsigning.NativeSignerMaterialFormatFrostTBTCSignerV1 { + switch material.Format { + case frostsigning.NativeSignerMaterialFormatFrostTBTCSignerV1: + case frostsigning.NativeSignerMaterialFormatFrostUniFFIV2: + return [32]byte{}, false, fmt.Errorf( + "%w: unsupported UniFFI FROST signer material format [%s]; "+ + "it cannot sweep Taproot deposits; use [%s]", + frostsigning.ErrUnsupportedSignerMaterialFormat, + frostsigning.NativeSignerMaterialFormatFrostUniFFIV2, + frostsigning.NativeSignerMaterialFormatFrostTBTCSignerV1, + ) + default: return [32]byte{}, false, nil } - if material.Format == frostsigning.NativeSignerMaterialFormatFrostTBTCSignerV1 { - var payload frostsigning.NativeTBTCSignerMaterialPayload - if err := json.Unmarshal(material.Payload, &payload); err != nil { - return [32]byte{}, false, fmt.Errorf( - "cannot decode FrostTBTCSignerV1 signer material: [%w]", - err, - ) - } + var payload frostsigning.NativeTBTCSignerMaterialPayload + if err := json.Unmarshal(material.Payload, &payload); err != nil { + return [32]byte{}, false, fmt.Errorf( + "cannot decode FrostTBTCSignerV1 signer material: [%w]", + err, + ) + } - if payload.KeyGroupSource == - frostsigning.NativeTBTCSignerKeyGroupSourceLegacyWalletPubKey { - return [32]byte{}, false, nil - } + if payload.KeyGroupSource == + frostsigning.NativeTBTCSignerKeyGroupSourceLegacyWalletPubKey { + return [32]byte{}, false, nil } xOnlyOutputKey, err := frostsigning.ExtractTaprootOutputKeyFromMaterial( diff --git a/pkg/tbtc/wallet_id_from_signer_frost_native_test.go b/pkg/tbtc/wallet_id_from_signer_frost_native_test.go index f51fe95706..e3d358ae92 100644 --- a/pkg/tbtc/wallet_id_from_signer_frost_native_test.go +++ b/pkg/tbtc/wallet_id_from_signer_frost_native_test.go @@ -6,14 +6,13 @@ import ( "crypto/ecdsa" "encoding/hex" "encoding/json" + "errors" "testing" frostsigning "github.com/keep-network/keep-core/pkg/frost/signing" ) -func TestCalculateWalletIDForSigner_FrostUniFFIV2UsesXOnlyOutputKey(t *testing.T) { - const xOnlyOutputKey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - +func TestCalculateWalletIDForSigner_FrostUniFFIV2RejectsUnsupportedMaterial(t *testing.T) { payload, err := json.Marshal(struct { KeyPackage *frostsigning.NativeFROSTKeyPackage `json:"keyPackage"` PublicKeyPackage *frostsigning.NativeFROSTPublicKeyPackage `json:"publicKeyPackage"` @@ -23,7 +22,7 @@ func TestCalculateWalletIDForSigner_FrostUniFFIV2UsesXOnlyOutputKey(t *testing.T Data: []byte{0x01}, }, PublicKeyPackage: &frostsigning.NativeFROSTPublicKeyPackage{ - VerifyingKey: xOnlyOutputKey, + VerifyingKey: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }, }) if err != nil { @@ -36,30 +35,27 @@ func TestCalculateWalletIDForSigner_FrostUniFFIV2UsesXOnlyOutputKey(t *testing.T Payload: payload, } - walletID, err := calculateWalletIDForSigner( + legacyCalculatorCalled := false + _, err = calculateWalletIDForSigner( signer, func(_ *ecdsa.PublicKey) ([32]byte, error) { + legacyCalculatorCalled = true return [32]byte{0xff}, nil }, ) - if err != nil { - t.Fatalf("unexpected wallet ID calculation error: [%v]", err) + if err == nil { + t.Fatal("expected unsupported material error") } - - var expectedWalletID [32]byte - expectedBytes, err := hex.DecodeString(xOnlyOutputKey) - if err != nil { - t.Fatalf("unexpected hex decode error: [%v]", err) - } - copy(expectedWalletID[:], expectedBytes) - - if walletID != expectedWalletID { + if !errors.Is(err, frostsigning.ErrUnsupportedSignerMaterialFormat) { t.Fatalf( - "unexpected FROST wallet ID\nexpected: [0x%x]\nactual: [0x%x]", - expectedWalletID, - walletID, + "unexpected wallet ID calculation error\nexpected: [%v]\nactual: [%v]", + frostsigning.ErrUnsupportedSignerMaterialFormat, + err, ) } + if legacyCalculatorCalled { + t.Fatal("legacy wallet ID calculator should not have been called") + } } func TestCalculateWalletIDForSigner_TBTCSignerDkgPersistedUsesXOnlyOutputKey(