Skip to content

Commit 368a091

Browse files
committed
bip322: add message verification
This commit adds verification functions for the generic message signing protocol and also adds test cases for all common script types.
1 parent c1efa39 commit 368a091

10 files changed

Lines changed: 4756 additions & 106 deletions

bip322/bip322.go

Lines changed: 750 additions & 17 deletions
Large diffs are not rendered by default.

bip322/bip322_test.go

Lines changed: 625 additions & 87 deletions
Large diffs are not rendered by default.

bip322/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.25.0
55
require (
66
github.com/btcsuite/btcd/address/v2 v2.0.0
77
github.com/btcsuite/btcd/btcec/v2 v2.5.0
8+
github.com/btcsuite/btcd/btcutil/v2 v2.0.0
89
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0
910
github.com/btcsuite/btcd/chainhash/v2 v2.0.0
1011
github.com/btcsuite/btcd/psbt/v2 v2.0.0
@@ -14,7 +15,6 @@ require (
1415
)
1516

1617
require (
17-
github.com/btcsuite/btcd/btcutil/v2 v2.0.0 // indirect
1818
github.com/btcsuite/btclog v1.0.0 // indirect
1919
github.com/davecgh/go-spew v1.1.1 // indirect
2020
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect

bip322/signing.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,13 @@ func signInputWitness(packet *psbt.Packet, idx int, script []byte,
118118
if txIn.PreviousOutPoint.Index >= uint32(len(prevTx.TxOut)) {
119119
return fmt.Errorf("input %d has no UTXO", idx)
120120
}
121-
return fmt.Errorf("input %d has no witness UTXO", idx)
121+
122+
if prevTx.TxHash() != txIn.PreviousOutPoint.Hash {
123+
return fmt.Errorf("non witness utxo does not match " +
124+
"input prevout")
125+
}
126+
127+
utxo = prevTx.TxOut[txIn.PreviousOutPoint.Index]
122128
}
123129

124130
prevOutFetcher := PsbtPrevOutputFetcher(packet)

bip322/signing_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package bip322
2+
3+
import (
4+
mrand "math/rand/v2"
5+
"strings"
6+
"testing"
7+
"unicode/utf8"
8+
9+
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// randomUTF8String returns a string of n randomly-chosen valid UTF-8 runes.
14+
func randomUTF8String(n int) string {
15+
runes := make([]rune, n)
16+
for i := range runes {
17+
runes[i] = randomRune()
18+
}
19+
return string(runes)
20+
}
21+
22+
// randomRune returns a random valid Unicode scalar value. It rejects the
23+
// surrogate range (U+D800–U+DFFF) and anything above U+10FFFF, both of which
24+
// cannot be legally encoded as UTF-8.
25+
func randomRune() rune {
26+
for {
27+
// Random value in [0, 0x10FFFF].
28+
r := rune(mrand.Int64N(utf8.MaxRune + 1))
29+
if utf8.ValidRune(r) {
30+
return r
31+
}
32+
}
33+
}
34+
35+
// TestSign tests that a valid signature can be created for all supported
36+
// single-key address types.
37+
func TestSign(t *testing.T) {
38+
for range 100 {
39+
randString := randomUTF8String(256)
40+
41+
randPrivateKey, err := btcec.NewPrivateKey()
42+
require.NoError(t, err)
43+
44+
// Sign for a P2TR address.
45+
sigP2TR, err := SignP2TR(randString, randPrivateKey)
46+
require.NoError(t, err)
47+
require.Contains(t, sigP2TR, PrefixSimple)
48+
require.True(t, strings.HasPrefix(sigP2TR, PrefixSimple))
49+
50+
scriptP2TR, err := payToTaprootScript(randPrivateKey)
51+
require.NoError(t, err)
52+
53+
valid, _, err := verifyMessageForChallenge(
54+
[]byte(randString), scriptP2TR, sigP2TR,
55+
)
56+
require.NoError(t, err)
57+
require.True(t, valid)
58+
59+
// Sign for a P2WPKH address.
60+
sigP2WPKH, err := SignP2WPKH(randString, randPrivateKey)
61+
require.NoError(t, err)
62+
require.Contains(t, sigP2WPKH, PrefixSimple)
63+
require.True(t, strings.HasPrefix(sigP2WPKH, PrefixSimple))
64+
65+
scriptP2WPKH, err := payToWitnessPubKeyHashScript(
66+
randPrivateKey,
67+
)
68+
require.NoError(t, err)
69+
70+
valid, _, err = verifyMessageForChallenge(
71+
[]byte(randString), scriptP2WPKH, sigP2WPKH,
72+
)
73+
require.NoError(t, err)
74+
require.True(t, valid)
75+
76+
// Sign for a NP2WPKH address.
77+
sigNP2WPKH, err := SignNestedP2WPKH(randString, randPrivateKey)
78+
require.NoError(t, err)
79+
require.Contains(t, sigNP2WPKH, PrefixFull)
80+
require.True(t, strings.HasPrefix(sigNP2WPKH, PrefixFull))
81+
82+
scriptNP2WPKH, _, err := payToNestedWitnessPubKeyHashScript(
83+
randPrivateKey,
84+
)
85+
require.NoError(t, err)
86+
87+
valid, _, err = verifyMessageForChallenge(
88+
[]byte(randString), scriptNP2WPKH, sigNP2WPKH,
89+
)
90+
require.NoError(t, err)
91+
require.True(t, valid)
92+
93+
// Sign for a P2PKH address.
94+
sigP2PKH, err := SignP2PKH(randString, randPrivateKey)
95+
require.NoError(t, err)
96+
require.Contains(t, sigP2PKH, PrefixFull)
97+
require.True(t, strings.HasPrefix(sigP2PKH, PrefixFull))
98+
99+
scriptP2PKH, err := payToPubKeyHashScript(randPrivateKey)
100+
require.NoError(t, err)
101+
102+
valid, _, err = verifyMessageForChallenge(
103+
[]byte(randString), scriptP2PKH, sigP2PKH,
104+
)
105+
require.NoError(t, err)
106+
require.True(t, valid)
107+
}
108+
}

0 commit comments

Comments
 (0)