diff --git a/mixing/signatures.go b/mixing/signatures.go index 4351b52b1..313435d8e 100644 --- a/mixing/signatures.go +++ b/mixing/signatures.go @@ -1,18 +1,17 @@ -// Copyright (c) 2023-2024 The Decred developers +// Copyright (c) 2023-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package mixing import ( - "bytes" - "fmt" + "encoding/hex" "hash" + "strconv" "github.com/decred/dcrd/crypto/blake256" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" - "github.com/decred/dcrd/wire" ) const tag = "decred-mix-signature" @@ -46,8 +45,6 @@ func VerifySignedMessage(m Signed) bool { m.WriteSignedData(h) sigHash := h.Sum256() - h.Reset() - command := m.Command() sid := m.Sid() run := m.GetRun() @@ -56,6 +53,7 @@ func VerifySignedMessage(m Signed) bool { run = 0 } + h.Reset() return verify(h, m.Pub(), m.Sig(), sigHash[:], command, sid, run) } @@ -76,8 +74,6 @@ func sign(priv *secp256k1.PrivateKey, m Signed) ([]byte, error) { m.WriteSignedData(h) sigHash := h.Sum256() - h.Reset() - sid := m.Sid() run := m.GetRun() if len(sid) != 32 { @@ -85,17 +81,8 @@ func sign(priv *secp256k1.PrivateKey, m Signed) ([]byte, error) { run = 0 } - buf := new(bytes.Buffer) - buf.Grow(len(tag) + wire.CommandSize + - 64 + // sid - 4 + // run - 64 + // sigHash - 4, // commas - ) - fmt.Fprintf(buf, tag+",%s,%x,%d,%x", m.Command(), sid, run, sigHash[:]) - h.Write(buf.Bytes()) - - hash := h.Sum256() + h.Reset() + hash := schnorrHash(h, m.Command(), sid, run, sigHash[:]) sig, err := schnorr.Sign(priv, hash[:]) if err != nil { return nil, err @@ -116,17 +103,21 @@ func verify(h *blake256.Hasher256, pk []byte, sig []byte, sigHash []byte, comman return false } - h.Reset() - - buf := new(bytes.Buffer) - buf.Grow(len(tag) + wire.CommandSize + - 64 + // sid - 4 + // run - 64 + // sigHash - 4, // commas - ) - fmt.Fprintf(buf, tag+",%s,%x,%d,%x", command, sid, run, sigHash) - h.Write(buf.Bytes()) - hash := h.Sum256() + hash := schnorrHash(h, command, sid, run, sigHash) return sigParsed.Verify(hash[:], pkParsed) } + +func schnorrHash(h *blake256.Hasher256, command string, sid []byte, run uint32, sigHash []byte) [32]byte { + buf := make([]byte, 64) + + h.WriteBytes([]byte(tag)) + h.WriteByte(',') + h.WriteString(command) + h.WriteByte(',') + h.WriteBytes(hex.AppendEncode(buf[:0], sid)) + h.WriteByte(',') + h.WriteBytes(strconv.AppendUint(buf[:0], uint64(run), 10)) + h.WriteByte(',') + h.WriteBytes(hex.AppendEncode(buf[:0], sigHash)) + return h.Sum256() +} diff --git a/mixing/signatures_test.go b/mixing/signatures_test.go new file mode 100644 index 000000000..4afb518e9 --- /dev/null +++ b/mixing/signatures_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package mixing + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/wire" +) + +var ( + testPrivKey = secp256k1.PrivKeyFromBytes([]byte{31: 1}) + testPubKey = testPrivKey.PubKey() +) + +func hexDecode(tb testing.TB, s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + tb.Fatalf("Hex decode failed: %v", err) + } + return b +} + +func fakePR(tb testing.TB) *wire.MsgMixPairReq { + var id [33]byte + copy(id[:], testPubKey.SerializeCompressed()) + utxos := []wire.MixPairReqUTXO{{ + OutPoint: wire.OutPoint{ + Hash: [32]byte{31: 1}, + Index: 2, + Tree: 3, + }, + Script: []byte{4, 5, 6}, + PubKey: []byte{31: 7}, + Signature: []byte{31: 8}, + Opcode: 9, + }} + change := &wire.TxOut{ + Value: 10, + Version: 11, + PkScript: []byte{12, 13, 14}, + } + pr, err := wire.NewMsgMixPairReq(id, 15, 16, "script class", 17, 18, 19, 20, utxos, change, 21, 22) + if err != nil { + tb.Fatal(err) + } + return pr +} + +const wantSig = "1ffdddfb26b2955b3cf46c9e98671d11d1fd91619da4e85edb840786f661b6ab6824c83923f596a990613fce97d995d9d50a89ecb23841c9c10a80a47f18b50a" + +func TestSignMessage(t *testing.T) { + pr := fakePR(t) + + if err := SignMessage(pr, testPrivKey); err != nil { + t.Fatalf("Failed to sign message: %v", err) + } + + if !bytes.Equal(pr.Signature[:], hexDecode(t, wantSig)) { + t.Fatalf("Signature does not match expected (got: %x want: %s)", pr.Signature[:], wantSig) + } +} + +func TestVerifySignedMessage(t *testing.T) { + pr := fakePR(t) + + if err := SignMessage(pr, testPrivKey); err != nil { + t.Fatalf("Failed to sign message: %v", err) + } + + if !VerifySignedMessage(pr) { + t.Fatalf("VerifySignedMessage invalid signature %x", pr.Signature[:]) + } +} + +func BenchmarkSignMessage(b *testing.B) { + pr := fakePR(b) + + for b.Loop() { + err := SignMessage(pr, testPrivKey) + if err != nil { + b.Fatalf("Failed to sign message: %v", err) + } + } +} + +func BenchmarkVerifySignedMessage(b *testing.B) { + pr := fakePR(b) + err := SignMessage(pr, testPrivKey) + if err != nil { + b.Fatalf("Failed to sign message: %v", err) + } + + for b.Loop() { + if !VerifySignedMessage(pr) { + b.Fatalf("VerifySignedMessage invalid signature %x", pr.Signature[:]) + } + } +}