Skip to content

Commit 6e8bdd3

Browse files
Merge commit from fork
* init * pr comments * simplify address type parsing * changelog * compare sighash types rather than raw bytes * follow camel case for naming * add vanilla upgrade handler for v4.1 * add v4.1 upgrade to mainnet * cl
1 parent fd0e8fa commit 6e8bdd3

8 files changed

Lines changed: 260 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
4040
### Bug fixes
4141

4242
- [GHSA-2fcv-qww3-9v6h](https://github.com/babylonlabs-io/babylon-ghsa-2fcv-qww3-9v6h/pull/1) Add unkown fields check on vote extension validation
43+
- [GHSA-xq4h-wqm2-668w](https://github.com/babylonlabs-io/babylon-ghsa-xq4h-wqm2-668w/pull/2) crypto: ensure BIP-322 signatures are using `SIGHASH_ALL` or `SIGHASH_DEFAULT`
4344

4445
## v4.0.0
4546

app/include_upgrade_mainnet.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
v22 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v2_2"
1010
v23 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v2_3"
1111
v4 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v4"
12+
v41 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v4_1"
1213
)
1314

1415
var WhitelistedChannelsID = map[string]struct{}{
@@ -24,6 +25,7 @@ var WhitelistedChannelsID = map[string]struct{}{
2425
// init is used to include v2.2 upgrade for mainnet data
2526
func init() {
2627
Upgrades = []upgrades.Upgrade{
28+
v41.Upgrade,
2729
v4.Upgrade,
2830
v23.Upgrade, // same as v3rc3 testnet
2931
v22.Upgrade,

app/include_upgrade_testnet.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import (
1313
v23 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v2_3"
1414
v2rc4 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v2rc4/testnet"
1515
v4 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v4"
16+
v41 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v4_1"
1617
v4rc3 "github.com/babylonlabs-io/babylon/v4/app/upgrades/v4rc3/testnet"
1718
)
1819

1920
// init is used to include v1 upgrade testnet data
2021
// it is also used for e2e testing
2122
func init() {
2223
Upgrades = []upgrades.Upgrade{
24+
v41.Upgrade,
2325
v4rc3.Upgrade,
2426
v4.Upgrade,
2527
v23.Upgrade,

app/upgrades/v4_1/upgrade.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package testnet
2+
3+
import (
4+
"context"
5+
6+
store "cosmossdk.io/store/types"
7+
upgradetypes "cosmossdk.io/x/upgrade/types"
8+
"github.com/babylonlabs-io/babylon/v4/app/keepers"
9+
"github.com/babylonlabs-io/babylon/v4/app/upgrades"
10+
"github.com/cosmos/cosmos-sdk/types/module"
11+
)
12+
13+
const UpgradeName = "v4.1"
14+
15+
var Upgrade = upgrades.Upgrade{
16+
UpgradeName: UpgradeName,
17+
CreateUpgradeHandler: CreateUpgradeHandler,
18+
StoreUpgrades: store.StoreUpgrades{
19+
Added: []string{},
20+
Deleted: []string{},
21+
},
22+
}
23+
24+
func CreateUpgradeHandler(mm *module.Manager, configurator module.Configurator, keepers *keepers.AppKeepers) upgradetypes.UpgradeHandler {
25+
return func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
26+
// Run migrations before applying any other state changes.
27+
migrations, err := mm.RunMigrations(ctx, configurator, fromVM)
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
return migrations, nil
33+
}
34+
}

crypto/bip322/bip322.go

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package bip322
22

33
import (
44
"crypto/sha256"
5+
"fmt"
56

67
"github.com/btcsuite/btcd/btcec/v2"
78
"github.com/btcsuite/btcd/btcec/v2/schnorr"
@@ -139,11 +140,74 @@ func GetToSignTx(toSpend *wire.MsgTx) *wire.MsgTx {
139140
return toSign
140141
}
141142

142-
func Verify(
143+
// validateSigHashType validates that the witness uses an allowed SIGHASH type
144+
// according to BIP-322 specification.
145+
// For Taproot (P2TR): SIGHASH_DEFAULT (implicit, no byte) or SIGHASH_ALL (0x01)
146+
// For P2WPKH: SIGHASH_ALL (0x01)
147+
func validateSigHashType(witness wire.TxWitness, address btcutil.Address) error {
148+
if len(witness) == 0 {
149+
return fmt.Errorf("empty witness")
150+
}
151+
152+
// The signature is always in the first element of the witness
153+
sig := witness[0]
154+
if len(sig) == 0 {
155+
return fmt.Errorf("empty signature in witness")
156+
}
157+
158+
script, err := txscript.PayToAddrScript(address)
159+
if err != nil {
160+
return err
161+
}
162+
if txscript.IsPayToTaproot(script) {
163+
// For Taproot:
164+
// - SIGHASH_DEFAULT: signature is 64 bytes (no sighash byte appended)
165+
// - SIGHASH_ALL: signature is 65 bytes with 0x01 as the last byte
166+
switch len(sig) {
167+
case 64:
168+
// SIGHASH_DEFAULT - valid
169+
return nil
170+
case 65:
171+
// Must be SIGHASH_ALL (0x01)
172+
sighash := txscript.SigHashType(sig[64])
173+
if sighash != txscript.SigHashAll {
174+
return fmt.Errorf("invalid sighash type for taproot: 0x%02x, expected SIGHASH_ALL (0x01) or SIGHASH_DEFAULT", sighash)
175+
}
176+
return nil
177+
default:
178+
return fmt.Errorf("invalid taproot signature length: %d, expected 64 (SIGHASH_DEFAULT) or 65 (with sighash byte)", len(sig))
179+
}
180+
} else if txscript.IsPayToWitnessPubKeyHash(script) {
181+
// For P2WPKH: signature must end with SIGHASH_ALL (0x01)
182+
// DER-encoded ECDSA signature format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash]
183+
// Minimum length is ~70 bytes for ECDSA + 1 byte sighash
184+
if len(sig) < 9 {
185+
return fmt.Errorf("signature too short for P2WPKH: %d bytes", len(sig))
186+
}
187+
188+
// The last byte should be the sighash type
189+
sighash := txscript.SigHashType(sig[len(sig)-1])
190+
if sighash != txscript.SigHashAll {
191+
return fmt.Errorf("invalid sighash type for P2WPKH: 0x%02x, expected SIGHASH_ALL (0x01)", sighash)
192+
}
193+
return nil
194+
} else {
195+
return fmt.Errorf("unsupported address type: %T", address)
196+
}
197+
}
198+
199+
// VerifyP2WPKHAndP2TR validates a BIP-322 signature for either a P2WPKH or Taproot (P2TR) address.
200+
func VerifyP2WPKHAndP2TR(
143201
msg []byte,
144202
witness wire.TxWitness,
145203
address btcutil.Address,
146-
net *chaincfg.Params) error {
204+
net *chaincfg.Params,
205+
) error {
206+
// First, validate that the witness uses allowed SIGHASH types
207+
if err := validateSigHashType(witness, address); err != nil {
208+
return fmt.Errorf("sighash validation failed: %w", err)
209+
}
210+
147211
toSpend, err := GetToSpendTx(msg, address)
148212
if err != nil {
149213
return err

crypto/bip322/bip322_test.go

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/btcsuite/btcd/btcec/v2"
1212
"github.com/btcsuite/btcd/btcutil"
1313
"github.com/btcsuite/btcd/chaincfg"
14+
"github.com/btcsuite/btcd/txscript"
1415
"github.com/stretchr/testify/require"
1516
)
1617

@@ -67,7 +68,7 @@ func TestBIP322_Verify(t *testing.T) {
6768
witness, err := bip322.SimpleSigToWitness(emptyBytesSig)
6869
require.NoError(t, err)
6970

70-
err = bip322.Verify(msg, witness, addressDecoded, net)
71+
err = bip322.VerifyP2WPKHAndP2TR(msg, witness, addressDecoded, net)
7172
require.NoError(t, err)
7273
}
7374

@@ -84,7 +85,7 @@ func FuzzBip322ValidP2WPKHSignature(f *testing.F) {
8485
witnessDecoded, err := bip322.SimpleSigToWitness(witness)
8586
require.NoError(t, err)
8687

87-
err = bip322.Verify(
88+
err = bip322.VerifyP2WPKHAndP2TR(
8889
dataToSign,
8990
witnessDecoded,
9091
address,
@@ -107,7 +108,7 @@ func FuzzBip322ValidP2TrSpendSignature(f *testing.F) {
107108
witnessDecoded, err := bip322.SimpleSigToWitness(witness)
108109
require.NoError(t, err)
109110

110-
err = bip322.Verify(
111+
err = bip322.VerifyP2WPKHAndP2TR(
111112
dataToSign,
112113
witnessDecoded,
113114
address,
@@ -116,3 +117,126 @@ func FuzzBip322ValidP2TrSpendSignature(f *testing.F) {
116117
require.NoError(t, err)
117118
})
118119
}
120+
121+
func FuzzBip322SigHashTypeP2WPKH(f *testing.F) {
122+
// Add corpus entries: seed and sigHashType
123+
// Valid SIGHASH types
124+
f.Add(int64(1), uint8(0x01)) // SIGHASH_ALL - valid
125+
// Invalid SIGHASH types
126+
f.Add(int64(2), uint8(0x02)) // SIGHASH_NONE
127+
f.Add(int64(3), uint8(0x03)) // SIGHASH_SINGLE
128+
f.Add(int64(4), uint8(0x81)) // SIGHASH_ALL_ANYONECANPAY
129+
f.Add(int64(5), uint8(0x82)) // SIGHASH_NONE_ANYONECANPAY
130+
f.Add(int64(6), uint8(0x83)) // SIGHASH_SINGLE_ANYONECANPAY
131+
132+
f.Fuzz(func(t *testing.T, seed int64, sigHashTypeByte uint8) {
133+
r := rand.New(rand.NewSource(seed))
134+
privkey, err := btcec.NewPrivateKey()
135+
require.NoError(t, err)
136+
137+
dataLen := r.Int31n(200) + 1
138+
dataToSign := datagen.GenRandomByteArray(r, uint64(dataLen))
139+
140+
sigHashType := txscript.SigHashType(sigHashTypeByte)
141+
address, witness, err := datagen.SignWithP2WPKHAddressWithSigHashType(
142+
dataToSign,
143+
privkey,
144+
net,
145+
sigHashType,
146+
)
147+
148+
// btcd's WitnessSignature does not validate SIGHASH types during signing for P2WPKH
149+
// It allows any SIGHASH type to be signed, leaving validation to script execution
150+
// Therefore, signing should ALWAYS succeed regardless of SIGHASH type
151+
require.NoError(t, err, "P2WPKH signing should always succeed with any sighash type, got error for 0x%02x: %v", sigHashTypeByte, err)
152+
153+
witnessDecoded, err := bip322.SimpleSigToWitness(witness)
154+
require.NoError(t, err)
155+
156+
err = bip322.VerifyP2WPKHAndP2TR(
157+
dataToSign,
158+
witnessDecoded,
159+
address,
160+
net,
161+
)
162+
163+
// BIP-322 requires SIGHASH_ALL for P2WPKH
164+
// btcd allowed signing with any sighash type (no validation during signing)
165+
// Our verification layer must enforce the BIP-322 requirement
166+
if sigHashType == txscript.SigHashAll {
167+
require.NoError(t, err, "BIP-322 should accept SIGHASH_ALL (0x01) for P2WPKH")
168+
} else {
169+
// btcd allowed signing, but our BIP-322 verification must reject
170+
require.Error(t, err, "BIP-322 should reject sighash type 0x%02x for P2WPKH (only SIGHASH_ALL is allowed)", sigHashTypeByte)
171+
require.Contains(t, err.Error(), "sighash validation failed")
172+
}
173+
})
174+
}
175+
176+
func FuzzBip322SigHashTypeP2TR(f *testing.F) {
177+
// Add corpus entries: seed and sigHashType
178+
// Valid SIGHASH types
179+
f.Add(int64(1), uint8(0x00)) // SIGHASH_DEFAULT - valid
180+
f.Add(int64(2), uint8(0x01)) // SIGHASH_ALL - valid
181+
// Invalid SIGHASH types
182+
f.Add(int64(3), uint8(0x02)) // SIGHASH_NONE
183+
f.Add(int64(4), uint8(0x03)) // SIGHASH_SINGLE
184+
f.Add(int64(5), uint8(0x81)) // SIGHASH_ALL_ANYONECANPAY
185+
f.Add(int64(6), uint8(0x82)) // SIGHASH_NONE_ANYONECANPAY
186+
f.Add(int64(7), uint8(0x83)) // SIGHASH_SINGLE_ANYONECANPAY
187+
188+
f.Fuzz(func(t *testing.T, seed int64, sigHashTypeByte uint8) {
189+
r := rand.New(rand.NewSource(seed))
190+
privkey, err := btcec.NewPrivateKey()
191+
require.NoError(t, err)
192+
193+
dataLen := r.Int31n(200) + 1
194+
dataToSign := datagen.GenRandomByteArray(r, uint64(dataLen))
195+
196+
sigHashType := txscript.SigHashType(sigHashTypeByte)
197+
address, witness, err := datagen.SignWithP2TrSpendAddressWithSigHashType(
198+
dataToSign,
199+
privkey,
200+
net,
201+
sigHashType,
202+
)
203+
204+
// btcd's TaprootWitnessSignature validates SIGHASH types according to BIP 341
205+
// Valid Taproot sighash types are: 0x00-0x03 (DEFAULT, ALL, NONE, SINGLE)
206+
// and 0x81-0x83 (with ANYONECANPAY flag)
207+
isValidTaprootSigHash := (sigHashTypeByte <= 0x03) ||
208+
(sigHashTypeByte >= 0x81 && sigHashTypeByte <= 0x83)
209+
210+
if isValidTaprootSigHash {
211+
// btcd should allow signing with any valid BIP 341 sighash type
212+
require.NoError(t, err, "Signing with valid Taproot sighash type 0x%02x should succeed: %v", sigHashTypeByte, err)
213+
} else {
214+
// btcd should reject invalid sighash types during signing for Taproot
215+
require.Error(t, err, "btcd should reject invalid Taproot sighash type 0x%02x during signing", sigHashTypeByte)
216+
// Can't test our verification layer if btcd rejects during signing
217+
return
218+
}
219+
220+
witnessDecoded, err := bip322.SimpleSigToWitness(witness)
221+
require.NoError(t, err)
222+
223+
err = bip322.VerifyP2WPKHAndP2TR(
224+
dataToSign,
225+
witnessDecoded,
226+
address,
227+
net,
228+
)
229+
230+
// BIP-322 is more restrictive than BIP 341 for Taproot
231+
// BIP 341 allows: 0x00-0x03, 0x81-0x83 (btcd validated this during signing)
232+
// BIP-322 only allows: 0x00 (DEFAULT) and 0x01 (ALL)
233+
// Our verification layer must reject all other sighash types
234+
if sigHashType == txscript.SigHashDefault || sigHashType == txscript.SigHashAll {
235+
require.NoError(t, err, "BIP-322 should accept SIGHASH_DEFAULT (0x00) and SIGHASH_ALL (0x01) for P2TR")
236+
} else {
237+
// btcd allowed signing (valid BIP 341), but our BIP-322 verification must reject
238+
require.Error(t, err, "BIP-322 should reject sighash type 0x%02x for P2TR (even though it's valid in BIP 341)", sigHashTypeByte)
239+
require.Contains(t, err.Error(), "sighash validation failed")
240+
}
241+
})
242+
}

testutil/datagen/pop.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ func SignWithP2WPKHAddress(
3232
msg []byte,
3333
privKey *btcec.PrivateKey,
3434
net *chaincfg.Params,
35+
) (*btcutil.AddressWitnessPubKeyHash, []byte, error) {
36+
return SignWithP2WPKHAddressWithSigHashType(msg, privKey, net, txscript.SigHashAll)
37+
}
38+
39+
func SignWithP2TrSpendAddress(
40+
msg []byte,
41+
privKey *btcec.PrivateKey,
42+
net *chaincfg.Params,
43+
) (*btcutil.AddressTaproot, []byte, error) {
44+
return SignWithP2TrSpendAddressWithSigHashType(msg, privKey, net, txscript.SigHashDefault)
45+
}
46+
47+
// SignWithP2WPKHAddressWithSigHashType creates a BIP-322 signature with a custom SIGHASH type for P2WPKH
48+
// This is useful for testing invalid SIGHASH types
49+
func SignWithP2WPKHAddressWithSigHashType(
50+
msg []byte,
51+
privKey *btcec.PrivateKey,
52+
net *chaincfg.Params,
53+
sigHashType txscript.SigHashType,
3554
) (*btcutil.AddressWitnessPubKeyHash, []byte, error) {
3655
pubKey := privKey.PubKey()
3756

@@ -56,9 +75,9 @@ func SignWithP2WPKHAddress(
5675

5776
hashCache := txscript.NewTxSigHashes(toSign, fetcher)
5877

59-
// always use compressed pubkey
78+
// Use the custom sigHashType parameter
6079
witness, err := txscript.WitnessSignature(toSign, hashCache, 0,
61-
toSpend.TxOut[0].Value, toSpend.TxOut[0].PkScript, txscript.SigHashAll, privKey, true)
80+
toSpend.TxOut[0].Value, toSpend.TxOut[0].PkScript, sigHashType, privKey, true)
6281

6382
if err != nil {
6483
return nil, nil, err
@@ -73,10 +92,13 @@ func SignWithP2WPKHAddress(
7392
return witnessAddr, serializedWitness, nil
7493
}
7594

76-
func SignWithP2TrSpendAddress(
95+
// SignWithP2TrSpendAddressWithSigHashType creates a BIP-322 signature with a custom SIGHASH type for P2TR
96+
// This is useful for testing invalid SIGHASH types
97+
func SignWithP2TrSpendAddressWithSigHashType(
7798
msg []byte,
7899
privKey *btcec.PrivateKey,
79100
net *chaincfg.Params,
101+
sigHashType txscript.SigHashType,
80102
) (*btcutil.AddressTaproot, []byte, error) {
81103
pubKey := privKey.PubKey()
82104

@@ -101,9 +123,10 @@ func SignWithP2TrSpendAddress(
101123

102124
hashCache := txscript.NewTxSigHashes(toSign, fetcher)
103125

126+
// Use the custom sigHashType parameter
104127
witness, err := txscript.TaprootWitnessSignature(
105128
toSign, hashCache, 0, toSpend.TxOut[0].Value, toSpend.TxOut[0].PkScript,
106-
txscript.SigHashDefault, privKey,
129+
sigHashType, privKey,
107130
)
108131

109132
if err != nil {

x/btcstaking/types/pop.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func VerifyBIP322SigPop(
202202
return err
203203
}
204204

205-
if err := bip322.Verify(msg, witness, btcAddress, net); err != nil {
205+
if err := bip322.VerifyP2WPKHAndP2TR(msg, witness, btcAddress, net); err != nil {
206206
return err
207207
}
208208

0 commit comments

Comments
 (0)