Skip to content

Commit

Permalink
Reivew fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
martonp committed Sep 3, 2024
1 parent c0b6848 commit b1c30e8
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 57 deletions.
91 changes: 56 additions & 35 deletions internal/adaptorsigs/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// AdaptorSignatureSize is the size of an encoded adaptor Schnorr signature.
const AdaptorSignatureSize = 129
const AdaptorSignatureSize = 97

// scalarSize is the size of an encoded big endian scalar.
const scalarSize = 32
Expand All @@ -30,6 +30,19 @@ var (
}
)

type affinePoint struct {
x secp256k1.FieldVal
y secp256k1.FieldVal
}

func (p *affinePoint) asJacobian() *secp256k1.JacobianPoint {
var result secp256k1.JacobianPoint
result.X.Set(&p.x)
result.Y.Set(&p.y)
result.Z.SetInt(1)
return &result
}

// AdaptorSignature is a signature with auxillary data that commits to a hidden
// value. When an adaptor signature is combined with a corresponding signature,
// the hidden value is revealed. Alternatively, when combined with a hidden
Expand All @@ -54,35 +67,32 @@ var (
// party B can recover the tweak and use it to decrypt the private key
// tweaked adaptor signature that party A originally sent them.
type AdaptorSignature struct {
r secp256k1.FieldVal
s secp256k1.ModNScalar
// t will always be in affine coordinates.
t secp256k1.JacobianPoint
r secp256k1.FieldVal
s secp256k1.ModNScalar
t affinePoint
pubKeyTweak bool
}

// Serialize returns a serialized adaptor signature in the following format:
//
// sig[0:32] x coordinate of the point R, encoded as a big-endian uint256
// sig[32:64] s, encoded also as big-endian uint256
// sig[64:96] x coordinate of the point T, encoded as a big-endian uint256
// sig[96:128] y coordinate of the point T, encoded as a big-endian uint256
// sig[128] 1 if the adaptor was created with a public key tweak, 0 if it was
// created with a private key tweak.
// sig[0:32] x coordinate of the point R, encoded as a big-endian uint256
// sig[32:64] s, encoded also as big-endian uint256
// sig[64:96] x coordinate of the point T, encoded as a big-endian uint256
// sig[96] first bit is 1 if the signature is public key tweaked, second bit
// is 1 if the y coordinate of T is odd.
func (sig *AdaptorSignature) Serialize() []byte {
var b [AdaptorSignatureSize]byte
sig.r.PutBytesUnchecked(b[0:32])
sig.s.PutBytesUnchecked(b[32:64])
sig.t.X.PutBytesUnchecked(b[64:96])
sig.t.Y.PutBytesUnchecked(b[96:128])
sig.t.x.PutBytesUnchecked(b[64:96])
if sig.pubKeyTweak {
b[128] = 1
} else {
b[128] = 0
b[96] = 1
}
b[96] |= byte(sig.t.y.IsOddBit()) << 1
return b[:]
}

// ParseAdaptorSignature parses an adaptor signature from a serialized format.
func ParseAdaptorSignature(b []byte) (*AdaptorSignature, error) {
if len(b) != AdaptorSignatureSize {
str := fmt.Sprintf("malformed signature: wrong size: %d", len(b))
Expand All @@ -101,20 +111,21 @@ func ParseAdaptorSignature(b []byte) (*AdaptorSignature, error) {
return nil, errors.New(str)
}

var t secp256k1.JacobianPoint
if overflow := t.X.SetBytes((*[32]byte)(b[64:96])); overflow > 0 {
var t affinePoint
if overflow := t.x.SetBytes((*[32]byte)(b[64:96])); overflow > 0 {
str := "invalid signature: t.x >= field prime"
return nil, errors.New(str)
}
t.x.Normalize()

if overflow := t.Y.SetBytes((*[32]byte)(b[96:128])); overflow > 0 {
str := "invalid signature: t.y >= field prime"
isOdd := (b[96]>>1)&1 == 1
if valid := secp256k1.DecompressY(&t.x, isOdd, &t.y); !valid {
str := "invalid signature: not for a valid curve point"
return nil, errors.New(str)
}
t.y.Normalize()

t.Z.SetInt(1)

pubKeyTweak := b[128] == byte(1)
pubKeyTweak := b[96]&1 == 1

return &AdaptorSignature{
r: r,
Expand All @@ -124,6 +135,15 @@ func ParseAdaptorSignature(b []byte) (*AdaptorSignature, error) {
}, nil
}

// IsEqual returns true if the adaptor signature is equal to another.
func (sig *AdaptorSignature) IsEqual(otherSig *AdaptorSignature) bool {
return sig.r.Equals(&otherSig.r) &&
sig.s.Equals(&otherSig.s) &&
sig.t.x.Equals(&otherSig.t.x) &&
sig.t.y.Equals(&otherSig.t.y) &&
sig.pubKeyTweak == otherSig.pubKeyTweak
}

// schnorrAdaptorVerify verifies that the adaptor signature will result in a
// valid signature when decrypted with the tweak.
func schnorrAdaptorVerify(sig *AdaptorSignature, hash []byte, pubKey *secp256k1.PublicKey) error {
Expand Down Expand Up @@ -195,9 +215,9 @@ func schnorrAdaptorVerify(sig *AdaptorSignature, hash []byte, pubKey *secp256k1.
secp256k1.ScalarBaseMultNonConst(&sig.s, &sG)
secp256k1.ScalarMultNonConst(&e, &Q, &eQ)
secp256k1.AddNonConst(&sG, &eQ, &R)
tInv := sig.t
tInv := sig.t.asJacobian()
tInv.Y.Negate(1)
secp256k1.AddNonConst(&R, &tInv, &encryptedR)
secp256k1.AddNonConst(&R, tInv, &encryptedR)

// Step 8.
//
Expand Down Expand Up @@ -231,7 +251,8 @@ func schnorrAdaptorVerify(sig *AdaptorSignature, hash []byte, pubKey *secp256k1.
return nil
}

// Verify checks that the adaptor signature, when decrypted using the ke
// Verify checks that the adaptor signature, when decrypted using the tweak,
// will result in a valid schnorr signature for the given hash and public key.
func (sig *AdaptorSignature) Verify(hash []byte, pubKey *secp256k1.PublicKey) error {
if sig.pubKeyTweak {
return fmt.Errorf("only priv key tweaked adaptors can be verified")
Expand All @@ -247,14 +268,14 @@ func (sig *AdaptorSignature) Decrypt(tweak *secp256k1.ModNScalar) (*schnorr.Sign
var expectedT secp256k1.JacobianPoint
secp256k1.ScalarBaseMultNonConst(tweak, &expectedT)
expectedT.ToAffine()
if !expectedT.X.Equals(&sig.t.X) {
if !expectedT.X.Equals(&sig.t.x) {
return nil, fmt.Errorf("tweak X does not match expected")
}
if !expectedT.Y.Equals(&sig.t.Y) {
if !expectedT.Y.Equals(&sig.t.y) {
return nil, fmt.Errorf("tweak Y does not match expected")
}

s := new(secp256k1.ModNScalar).Add(tweak)
s := new(secp256k1.ModNScalar).Set(tweak)
if !sig.pubKeyTweak {
s.Negate()
}
Expand All @@ -278,10 +299,10 @@ func (sig *AdaptorSignature) RecoverTweak(validSig *schnorr.Signature) (*secp256
var expectedT secp256k1.JacobianPoint
secp256k1.ScalarBaseMultNonConst(t, &expectedT)
expectedT.ToAffine()
if !expectedT.X.Equals(&sig.t.X) {
if !expectedT.X.Equals(&sig.t.x) {
return nil, fmt.Errorf("recovered tweak does not match expected")
}
if !expectedT.Y.Equals(&sig.t.Y) {
if !expectedT.Y.Equals(&sig.t.y) {
return nil, fmt.Errorf("recovered tweak does not match expected")
}

Expand All @@ -290,8 +311,7 @@ func (sig *AdaptorSignature) RecoverTweak(validSig *schnorr.Signature) (*secp256

// PublicTweak returns the hidden value multiplied by the generator point.
func (sig *AdaptorSignature) PublicTweak() *secp256k1.JacobianPoint {
T := sig.t
return &T
return sig.t.asJacobian()
}

// schnorrEncryptedSign creates an adaptor signature by modifying the nonce in
Expand Down Expand Up @@ -375,10 +395,11 @@ func schnorrEncryptedSign(privKey, nonce *secp256k1.ModNScalar, hash []byte, T *
affineT := new(secp256k1.JacobianPoint)
affineT.Set(T)
affineT.ToAffine()
t := affinePoint{x: affineT.X, y: affineT.Y}
return &AdaptorSignature{
r: *r,
s: *s,
t: *affineT,
t: t,
pubKeyTweak: true}, nil
}

Expand Down Expand Up @@ -484,6 +505,6 @@ func PrivateKeyTweakedAdaptorSig(sig *schnorr.Signature, pubKey *secp256k1.Publi
return &AdaptorSignature{
r: *r,
s: *tweakedS,
t: *T,
t: affinePoint{x: T.X, y: T.Y},
}
}
84 changes: 62 additions & 22 deletions internal/adaptorsigs/adaptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"
"time"

"decred.org/dcrdex/dex/encode"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr"
)
Expand All @@ -19,19 +20,15 @@ func TestAdaptorSignatureRandom(t *testing.T) {
}(t, seed)

for i := 0; i < 100; i++ {
// Generate two private keys
var pkBuf1, pkBuf2 [32]byte
if _, err := rng.Read(pkBuf1[:]); err != nil {
// Generate two private keys.
privKey1, err := secp256k1.GeneratePrivateKeyFromRand(rng)
if err != nil {
t.Fatalf("failed to read random private key: %v", err)
}
if _, err := rng.Read(pkBuf2[:]); err != nil {
privKey2, err := secp256k1.GeneratePrivateKeyFromRand(rng)
if err != nil {
t.Fatalf("failed to read random private key: %v", err)
}
var privKey1Scalar, privKey2Scalar secp256k1.ModNScalar
privKey1Scalar.SetBytes(&pkBuf1)
privKey2Scalar.SetBytes(&pkBuf2)
privKey1 := secp256k1.NewPrivateKey(&privKey1Scalar)
privKey2 := secp256k1.NewPrivateKey(&privKey2Scalar)

// Generate random hashes to sign.
var hash1, hash2 [32]byte
Expand Down Expand Up @@ -77,6 +74,9 @@ func TestAdaptorSignatureRandom(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !decryptedSig.Verify(hash2[:], privKey2.PubKey()) {
t.Fatal("failed to verify decrypted signature")
}

// Using the decrypted version of their sig, which has been made public,
// the owner of privKey2 can recover the tweak.
Expand Down Expand Up @@ -109,23 +109,63 @@ func RandomBytes(len int) []byte {
return bytes
}

func TestAdaptorSigParsing(t *testing.T) {
adaptor := &AdaptorSignature{}
adaptor.r.SetByteSlice(RandomBytes(32))
adaptor.s.SetByteSlice(RandomBytes(32))
adaptor.pubKeyTweak = true
func TestPublicKeyTweakParsing(t *testing.T) {
for i := 0; i < 100; i++ {
privKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
t.Fatal(err)
}
hash := encode.RandomBytes(32)
tweak, err := secp256k1.GeneratePrivateKey()
if err != nil {
t.Fatal(err)
}
var T secp256k1.JacobianPoint
tweak.PubKey().AsJacobian(&T)

var tweak secp256k1.JacobianPoint
secp256k1.ScalarBaseMultNonConst(&adaptor.s, &tweak)
adaptorSig, err := PublicKeyTweakedAdaptorSig(privKey, hash, &T)
if err != nil {
t.Fatal(err)
}

serialized := adaptor.Serialize()
serialized := adaptorSig.Serialize()
parsed, err := ParseAdaptorSignature(serialized)
if err != nil {
t.Fatal(err)
}

adaptor2, err := ParseAdaptorSignature(serialized)
if err != nil {
t.Fatal(err)
if !adaptorSig.IsEqual(parsed) {
t.Fatalf("parsed sig does not equal original")
}
}
}

func TestPrivateKeyTweakParsing(t *testing.T) {
for i := 0; i < 100; i++ {
privKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
t.Fatal(err)
}
hash := encode.RandomBytes(32)
tweak, err := secp256k1.GeneratePrivateKey()
if err != nil {
t.Fatal(err)
}

if !adaptor2.r.Equals(&adaptor.r) {
t.Fatal("r mismatch")
sig, err := schnorr.Sign(privKey, hash)
if err != nil {
t.Fatal(err)
}

adaptorSig := PrivateKeyTweakedAdaptorSig(sig, privKey.PubKey(), &tweak.Key)
serialized := adaptorSig.Serialize()
parsed, err := ParseAdaptorSignature(serialized)
if err != nil {
t.Fatal(err)
}

if !adaptorSig.IsEqual(parsed) {
t.Fatalf("parsed sig does not equal original")
}
}
}

0 comments on commit b1c30e8

Please sign in to comment.