Skip to content

Commit de9184a

Browse files
committed
implement adaptor signature verification
1 parent 6217d71 commit de9184a

File tree

8 files changed

+250
-33
lines changed

8 files changed

+250
-33
lines changed

crypto/adaptor/types.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package adaptor
2+
3+
import (
4+
"github.com/btcsuite/btcd/btcec/v2"
5+
)
6+
7+
// scalarSize is the size of an encoded big endian scalar
8+
const scalarSize = 32
9+
10+
// Signature is same with schnorr.Signature
11+
type Signature struct {
12+
r btcec.FieldVal
13+
s btcec.ModNScalar
14+
}
15+
16+
// NewSignature creates a new Signature from bytes
17+
// Assume that the given byte slice is valid
18+
func NewSignature(sigBytes []byte) *Signature {
19+
var r btcec.FieldVal
20+
_ = r.SetByteSlice(sigBytes[0:32])
21+
22+
var s btcec.ModNScalar
23+
_ = s.SetByteSlice(sigBytes[32:])
24+
25+
return &Signature{
26+
r,
27+
s,
28+
}
29+
}

crypto/adaptor/verifier.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package adaptor
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/btcsuite/btcd/btcec/v2"
7+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
8+
"github.com/btcsuite/btcd/chaincfg/chainhash"
9+
"github.com/decred/dcrd/dcrec/secp256k1/v4"
10+
)
11+
12+
// Verify verifies the provided schnorr adaptor signature against the given adaptor point
13+
func Verify(sigBytes []byte, msg []byte, pubKeyBytes []byte, adaptorPointBytes []byte) bool {
14+
_, err := schnorr.ParseSignature(sigBytes)
15+
if err != nil {
16+
return false
17+
}
18+
19+
pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
20+
if err != nil {
21+
return false
22+
}
23+
24+
adaptorPoint, err := btcec.ParsePubKey(adaptorPointBytes)
25+
if err != nil {
26+
return false
27+
}
28+
29+
return verifySchnorrAdaptorSignature(NewSignature(sigBytes), msg, pubKey, adaptorPoint) == nil
30+
}
31+
32+
// verifySchnorrAdaptorSignature verifies the given schnorr adaptor signature
33+
//
34+
// The algorithm is based on the verifier for schnorr signature
35+
// Specifically, step 3 and 6 are added, step 7 and 10 are modified
36+
//
37+
// Annotation:
38+
// AP: adaptor point
39+
// AR: adapted R
40+
func verifySchnorrAdaptorSignature(sig *Signature, hash []byte, pubKey *secp256k1.PublicKey, adaptorPoint *secp256k1.PublicKey) error {
41+
// 1. Fail if m is not 32 bytes
42+
// 2. P = lift_x(int(pk)).
43+
// 3. AP = lift_x(int(ap)).
44+
// 4. r = int(sig[0:32]); fail is r >= p.
45+
// 5. s = int(sig[32:64]); fail if s >= n.
46+
// 6. AR = R + AP.
47+
// 7. e = int(tagged_hash("BIP0340/challenge", bytes(AR) || bytes(P) || M)) mod n.
48+
// 8. R = s*G - e*P
49+
// 9. Fail if is_infinite(R)
50+
// 10. Fail if not has_even_y(R+AP)
51+
// 11. Fail is x(R) != r.
52+
// 12. Return success iff not failure occured before reaching this
53+
// point.
54+
55+
// Step 1.
56+
//
57+
// Fail if m is not 32 bytes
58+
if len(hash) != scalarSize {
59+
str := fmt.Sprintf("wrong size for message (got %v, want %v)",
60+
len(hash), scalarSize)
61+
return fmt.Errorf("invalid hash length: %s", str)
62+
}
63+
64+
// Step 2.
65+
//
66+
// P = lift_x(int(pk))
67+
//
68+
// Fail if P is not a point on the curve
69+
if !pubKey.IsOnCurve() {
70+
str := "pubkey point is not on curve"
71+
return fmt.Errorf("invalid pub key: %s", str)
72+
}
73+
74+
// Step 3.
75+
//
76+
// AP = lift_x(int(ap))
77+
//
78+
// Fail if AP is not a point on the curve
79+
if !adaptorPoint.IsOnCurve() {
80+
str := "adaptor point is not on curve"
81+
return fmt.Errorf("invalid adaptor point: %s", str)
82+
}
83+
84+
// Step 4.
85+
//
86+
// Fail if r >= p
87+
//
88+
// Note this is already handled by the fact r is a field element.
89+
90+
// Step 5.
91+
//
92+
// Fail if s >= n
93+
//
94+
// Note this is already handled by the fact s is a mod n scalar.
95+
96+
// Step 6.
97+
//
98+
// AR = R + AP
99+
var rBytes [32]byte
100+
sig.r.PutBytesUnchecked(rBytes[:])
101+
102+
rPoint, err := schnorr.ParsePubKey(rBytes[:])
103+
if err != nil {
104+
str := "failed to parse r"
105+
return fmt.Errorf("invalid r: %s", str)
106+
}
107+
108+
var R, AP, AR btcec.JacobianPoint
109+
rPoint.AsJacobian(&R)
110+
adaptorPoint.AsJacobian(&AP)
111+
btcec.AddNonConst(&R, &AP, &AR)
112+
113+
// Step 7.
114+
//
115+
// e = int(tagged_hash("BIP0340/challenge", bytes(ar) || bytes(P) || M)) mod n.
116+
AR.ToAffine()
117+
var arBytes [32]byte
118+
AR.X.PutBytesUnchecked(arBytes[:])
119+
120+
pBytes := schnorr.SerializePubKey(pubKey)
121+
122+
commitment := chainhash.TaggedHash(
123+
chainhash.TagBIP0340Challenge, arBytes[:], pBytes, hash,
124+
)
125+
126+
var e btcec.ModNScalar
127+
if overflow := e.SetBytes((*[32]byte)(commitment)); overflow != 0 {
128+
str := "hash of (r || P || m) too big"
129+
return fmt.Errorf("invalid schnorr hash: %s", str)
130+
}
131+
132+
// Negate e here so we can use AddNonConst below to subtract the s*G
133+
// point from e*P.
134+
e.Negate()
135+
136+
// Step 8.
137+
//
138+
// R = s*G - e*P
139+
var P, sG, eP btcec.JacobianPoint
140+
pubKey.AsJacobian(&P)
141+
btcec.ScalarBaseMultNonConst(&sig.s, &sG)
142+
btcec.ScalarMultNonConst(&e, &P, &eP)
143+
btcec.AddNonConst(&sG, &eP, &R)
144+
145+
// Step 9.
146+
//
147+
// Fail if R is the point at infinity
148+
if (R.X.IsZero() && R.Y.IsZero()) || R.Z.IsZero() {
149+
str := "calculated R point is the point at infinity"
150+
return fmt.Errorf("invalid signature: %s", str)
151+
}
152+
153+
// Step 10.
154+
//
155+
// Fail if (R+AP).y is odd
156+
//
157+
// Note that R+AP must be in affine coordinates for this check.
158+
btcec.AddNonConst(&R, &AP, &AR)
159+
AR.ToAffine()
160+
if AR.Y.IsOdd() {
161+
str := "calculated AR y-value is odd"
162+
return fmt.Errorf("invalid signature: %s", str)
163+
}
164+
165+
// Step 11.
166+
//
167+
// Verified if R.x == r
168+
//
169+
// Note that R must be in affine coordinates for this check.
170+
if !sig.r.Equals(&R.X) {
171+
str := "calculated R point was not given R"
172+
return fmt.Errorf("invalid signature: %s", str)
173+
}
174+
175+
// Step 12.
176+
//
177+
// Return success iff not failure occured before reaching this
178+
return nil
179+
}

crypto/hash/sha256.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package hash
2+
3+
import (
4+
"crypto/sha256"
5+
)
6+
7+
// Sha256 returns the SHA256 hash of the given data
8+
func Sha256(data []byte) []byte {
9+
hash := sha256.Sum256(data)
10+
11+
return hash[:]
12+
}

crypto/schnorr/verifier.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package schnorr
2+
3+
import (
4+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
5+
)
6+
7+
// Verify verifies the provided schnorr signature against the given msg and public key
8+
func Verify(sigBytes []byte, msg []byte, pubKeyBytes []byte) bool {
9+
signature, err := schnorr.ParseSignature(sigBytes)
10+
if err != nil {
11+
return false
12+
}
13+
14+
pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
15+
if err != nil {
16+
return false
17+
}
18+
19+
return signature.Verify(msg, pubKey)
20+
}

x/dlc/keeper/attestation.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
storetypes "cosmossdk.io/store/types"
88
sdk "github.com/cosmos/cosmos-sdk/types"
99

10+
"github.com/sideprotocol/side/crypto/hash"
11+
"github.com/sideprotocol/side/crypto/schnorr"
1012
"github.com/sideprotocol/side/x/dlc/types"
1113
)
1214

@@ -23,9 +25,9 @@ func (k Keeper) HandleAttestation(ctx sdk.Context, sender string, eventId uint64
2325

2426
pubKeyBytes, _ := hex.DecodeString(event.Pubkey)
2527
sigBytes, _ := hex.DecodeString(signature)
26-
msg := types.Sha256(sdk.Uint64ToBigEndian(event.TriggerPrice.Uint64()))
28+
msg := hash.Sha256(sdk.Uint64ToBigEndian(event.TriggerPrice.Uint64()))
2729

28-
if !types.VerifySchnorrSignature(sigBytes, msg, pubKeyBytes) {
30+
if !schnorr.Verify(sigBytes, msg, pubKeyBytes) {
2931
return errorsmod.Wrap(types.ErrInvalidSignature, "failed to verify the signature")
3032
}
3133

x/dlc/keeper/nonce.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
storetypes "cosmossdk.io/store/types"
1010
sdk "github.com/cosmos/cosmos-sdk/types"
1111

12+
"github.com/sideprotocol/side/crypto/hash"
13+
"github.com/sideprotocol/side/crypto/schnorr"
1214
"github.com/sideprotocol/side/x/dlc/types"
1315
)
1416

@@ -22,7 +24,7 @@ func (k Keeper) HandleNonce(ctx sdk.Context, sender string, nonce string, oracle
2224
nonceBytes, _ := hex.DecodeString(nonce)
2325
sigBytes, _ := hex.DecodeString(signature)
2426

25-
if !types.VerifySchnorrSignature(sigBytes, types.Sha256(nonceBytes), oraclePKBytes) {
27+
if !schnorr.Verify(sigBytes, hash.Sha256(nonceBytes), oraclePKBytes) {
2628
return errorsmod.Wrap(types.ErrInvalidSignature, "failed to verify the signature")
2729
}
2830

x/dlc/types/crypto.go

Lines changed: 0 additions & 29 deletions
This file was deleted.

x/dlc/types/dkg.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"crypto/ed25519"
66
"encoding/binary"
77
"slices"
8+
9+
"github.com/sideprotocol/side/crypto/hash"
810
)
911

1012
// ParticipantExists returns true if the given public key is a participant, false otherwise
@@ -36,7 +38,7 @@ func GetSigMsg(id uint64, pubKey []byte) []byte {
3638

3739
rawMsg = append(rawMsg, pubKey...)
3840

39-
return Sha256(rawMsg)
41+
return hash.Sha256(rawMsg)
4042
}
4143

4244
// VerifySignature verifies the given signature

0 commit comments

Comments
 (0)