Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package signing

import (
"context"
"fmt"
"math/big"
"sync"
"testing"
Expand Down Expand Up @@ -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,
) {
Expand Down
29 changes: 3 additions & 26 deletions pkg/frost/signing/attempt_context_from_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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.
//
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
//
Expand All @@ -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 {
Expand Down
76 changes: 22 additions & 54 deletions pkg/frost/signing/attempt_context_from_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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")
Expand All @@ -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("{}"),
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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")
Expand Down
Loading
Loading