diff --git a/go.mod b/go.mod index c4bab877fe..773061eef1 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.21 require ( decred.org/dcrwallet/v4 v4.1.1 + filippo.io/edwards25519 v1.0.0 fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 + github.com/athanorlabs/go-dleq v0.1.0 github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240625142744-cc26860b4026 github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/btcsuite/btcd/btcutil v1.1.5 diff --git a/go.sum b/go.sum index 084ee366e7..f54e0947b4 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ decred.org/cspp/v2 v2.2.0/go.mod h1:9nO3bfvCheOPIFZw5f6sRQ42CjBFB5RKSaJ9Iq6G4MA= decred.org/dcrwallet/v4 v4.1.1 h1:imwPBboytp1PH6V8q7/JLTHiKgj/Scq9a3I1WmnJv0Y= decred.org/dcrwallet/v4 v4.1.1/go.mod h1:WxerkRcUGVreJsAI0ptCBPUujPUmWncbdYbme8Kl5r0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4= fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= @@ -100,6 +102,8 @@ github.com/ashanbrown/forbidigo v1.1.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBF github.com/ashanbrown/forbidigo v1.2.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= github.com/ashanbrown/makezero v0.0.0-20210308000810-4155955488a0/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= +github.com/athanorlabs/go-dleq v0.1.0 h1:0/llWZG8fz2uintMBKOiBC502zCsDA8nt8vxI73W9Qc= +github.com/athanorlabs/go-dleq v0.1.0/go.mod h1:DWry6jSD7A13MKmeZA0AX3/xBeQCXDoygX99VPwL3yU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= diff --git a/internal/adaptorsigs/adaptor.go b/internal/adaptorsigs/adaptor.go new file mode 100644 index 0000000000..b268a231ce --- /dev/null +++ b/internal/adaptorsigs/adaptor.go @@ -0,0 +1,489 @@ +package adaptorsigs + +import ( + "errors" + "fmt" + + "github.com/decred/dcrd/crypto/blake256" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" +) + +// AdaptorSignatureSize is the size of an encoded adaptor Schnorr signature. +const AdaptorSignatureSize = 129 + +// scalarSize is the size of an encoded big endian scalar. +const scalarSize = 32 + +var ( + // rfc6979ExtraDataV0 is the extra data to feed to RFC6979 when generating + // the deterministic nonce for the EC-Schnorr-DCRv0 scheme. This ensures + // the same nonce is not generated for the same message and key as for other + // signing algorithms such as ECDSA. + // + // It is equal to BLAKE-256([]byte("EC-Schnorr-DCRv0")). + rfc6979ExtraDataV0 = [32]byte{ + 0x0b, 0x75, 0xf9, 0x7b, 0x60, 0xe8, 0xa5, 0x76, + 0x28, 0x76, 0xc0, 0x04, 0x82, 0x9e, 0xe9, 0xb9, + 0x26, 0xfa, 0x6f, 0x0d, 0x2e, 0xea, 0xec, 0x3a, + 0x4f, 0xd1, 0x44, 0x6a, 0x76, 0x83, 0x31, 0xcb, + } +) + +// 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 +// value, the adaptor reveals the signature. +// +// An adaptor signature is created by either doing a public or private key +// tweak of a valid schnorr signature. A private key tweak can only be done by +// a party who knows the hidden value, and a public key tweak can be done by +// a party that only knows the point on the secp256k1 curve derived by the +// multiplying the hidden value by the generator point. +// +// Generally the workflow of using adaptor signatures is the following: +// 1. Party A randomly selects a hidden value and creates a private key +// modified adaptor signature of something for which party B requires +// a valid signature. +// 2. The Party B sees the PublicTweak in the adaptor signature, and creates +// a public key tweaked adaptor signature for something that party A +// requires a valid signature. +// 3. Since party A knows the hidden value, they can use the hidden value to +// create a valid signature from the public key tweaked adaptor signature. +// 4. When the valid signature is revealed, by being posted to the blockchain, +// 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 + 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. +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]) + if sig.pubKeyTweak { + b[128] = 1 + } else { + b[128] = 0 + } + return b[:] +} + +func ParseAdaptorSignature(b []byte) (*AdaptorSignature, error) { + if len(b) != AdaptorSignatureSize { + str := fmt.Sprintf("malformed signature: wrong size: %d", len(b)) + return nil, errors.New(str) + } + + var r secp256k1.FieldVal + if overflow := r.SetBytes((*[32]byte)(b[0:32])); overflow > 0 { + str := "invalid signature: r >= field prime" + return nil, errors.New(str) + } + + var s secp256k1.ModNScalar + if overflow := s.SetBytes((*[32]byte)(b[32:64])); overflow > 0 { + str := "invalid signature: s >= group order" + return nil, errors.New(str) + } + + var t secp256k1.JacobianPoint + if overflow := t.X.SetBytes((*[32]byte)(b[64:96])); overflow > 0 { + str := "invalid signature: t.x >= field prime" + return nil, errors.New(str) + } + + if overflow := t.Y.SetBytes((*[32]byte)(b[96:128])); overflow > 0 { + str := "invalid signature: t.y >= field prime" + return nil, errors.New(str) + } + + t.Z.SetInt(1) + + pubKeyTweak := b[128] == byte(1) + + return &AdaptorSignature{ + r: r, + s: s, + t: t, + pubKeyTweak: pubKeyTweak, + }, nil +} + +// 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 { + // The algorithm for producing a EC-Schnorr-DCRv0 signature is as follows: + // This deviates from the original algorithm in step 7. + // + // 1. Fail if m is not 32 bytes + // 2. Fail if Q is not a point on the curve + // 3. Fail if r >= p + // 4. Fail if s >= n + // 5. e = BLAKE-256(r || m) (Ensure r is padded to 32 bytes) + // 6. Fail if e >= n + // 7. R = s*G + e*Q - T + // 8. Fail if R is the point at infinity + // 9. Fail if R.y is odd + // 10. Verified if R.x == r + + // Step 1. + // + // Fail if m is not 32 bytes + if len(hash) != scalarSize { + str := fmt.Sprintf("wrong size for message (got %v, want %v)", + len(hash), scalarSize) + return errors.New(str) + } + + // Step 2. + // + // Fail if Q is not a point on the curve + if !pubKey.IsOnCurve() { + str := "pubkey point is not on curve" + return errors.New(str) + } + + // Step 3. + // + // Fail if r >= p + // + // Note this is already handled by the fact r is a field element. + + // Step 4. + // + // Fail if s >= n + // + // Note this is already handled by the fact s is a mod n scalar. + + // Step 5. + // + // e = BLAKE-256(r || m) (Ensure r is padded to 32 bytes) + var commitmentInput [scalarSize * 2]byte + sig.r.PutBytesUnchecked(commitmentInput[0:scalarSize]) + copy(commitmentInput[scalarSize:], hash[:]) + commitment := blake256.Sum256(commitmentInput[:]) + + // Step 6. + // + // Fail if e >= n + var e secp256k1.ModNScalar + if overflow := e.SetBytes(&commitment); overflow != 0 { + str := "hash of (R || m) too big" + return errors.New(str) + } + + // Step 7. + // + // R = s*G + e*Q - T + var Q, R, sG, eQ, encryptedR secp256k1.JacobianPoint + pubKey.AsJacobian(&Q) + secp256k1.ScalarBaseMultNonConst(&sig.s, &sG) + secp256k1.ScalarMultNonConst(&e, &Q, &eQ) + secp256k1.AddNonConst(&sG, &eQ, &R) + tInv := sig.t + tInv.Y.Negate(1) + secp256k1.AddNonConst(&R, &tInv, &encryptedR) + + // Step 8. + // + // Fail if R is the point at infinity + if (encryptedR.X.IsZero() && encryptedR.Y.IsZero()) || encryptedR.Z.IsZero() { + str := "calculated R point is the point at infinity" + return errors.New(str) + } + + // Step 9. + // + // Fail if R.y is odd + // + // Note that R must be in affine coordinates for this check. + encryptedR.ToAffine() + if encryptedR.Y.IsOdd() { + str := "calculated encrypted R y-value is odd" + return errors.New(str) + } + + // Step 10. + // + // Verified if R.x == r + // + // Note that R must be in affine coordinates for this check. + if !sig.r.Equals(&encryptedR.X) { + str := "calculated R point was not given R" + return errors.New(str) + } + + return nil +} + +// Verify checks that the adaptor signature, when decrypted using the ke +func (sig *AdaptorSignature) Verify(hash []byte, pubKey *secp256k1.PublicKey) error { + if sig.pubKeyTweak { + return fmt.Errorf("only priv key tweaked adaptors can be verified") + } + + return schnorrAdaptorVerify(sig, hash, pubKey) +} + +// Decrypt returns a valid schnorr signature if the tweak is correct. +// This may not be a valid signature if the tweak is incorrect. The caller can +// use Verify to make sure it is a valid signature. +func (sig *AdaptorSignature) Decrypt(tweak *secp256k1.ModNScalar) (*schnorr.Signature, error) { + var expectedT secp256k1.JacobianPoint + secp256k1.ScalarBaseMultNonConst(tweak, &expectedT) + expectedT.ToAffine() + if !expectedT.X.Equals(&sig.t.X) { + return nil, fmt.Errorf("tweak X does not match expected") + } + if !expectedT.Y.Equals(&sig.t.Y) { + return nil, fmt.Errorf("tweak Y does not match expected") + } + + s := new(secp256k1.ModNScalar).Add(tweak) + if !sig.pubKeyTweak { + s.Negate() + } + s.Add(&sig.s) + + decryptedSig := schnorr.NewSignature(&sig.r, s) + return decryptedSig, nil +} + +// RecoverTweak recovers the tweak using the decrypted signature. +func (sig *AdaptorSignature) RecoverTweak(validSig *schnorr.Signature) (*secp256k1.ModNScalar, error) { + if !sig.pubKeyTweak { + return nil, fmt.Errorf("only pub key tweaked sigs can be recovered") + } + + _, s := parseSig(validSig) + + t := new(secp256k1.ModNScalar).NegateVal(&sig.s).Add(s) + + // Verify the recovered tweak + var expectedT secp256k1.JacobianPoint + secp256k1.ScalarBaseMultNonConst(t, &expectedT) + expectedT.ToAffine() + if !expectedT.X.Equals(&sig.t.X) { + return nil, fmt.Errorf("recovered tweak does not match expected") + } + if !expectedT.Y.Equals(&sig.t.Y) { + return nil, fmt.Errorf("recovered tweak does not match expected") + } + + return t, nil +} + +// PublicTweak returns the hidden value multiplied by the generator point. +func (sig *AdaptorSignature) PublicTweak() *secp256k1.JacobianPoint { + T := sig.t + return &T +} + +// schnorrEncryptedSign creates an adaptor signature by modifying the nonce in +// the commitment to be the sum of the nonce and the tweak. If the resulting +// signature is summed with the tweak, a valid signature is produced. +func schnorrEncryptedSign(privKey, nonce *secp256k1.ModNScalar, hash []byte, T *secp256k1.JacobianPoint) (*AdaptorSignature, error) { + // The algorithm for producing an encrypted EC-Schnorr-DCRv0 is as follows: + // The deviations from the original algorithm are in step 5 and 6. + // + // G = curve generator + // n = curve order + // d = private key + // m = message + // r, s = signature + // t = hidden value + // T = t * G + // + // 1. Fail if m is not 32 bytes + // 2. Fail if d = 0 or d >= n + // 3. Use RFC6979 to generate a deterministic nonce k in [1, n-1] + // parameterized by the private key, message being signed, extra data + // that identifies the scheme, and an iteration count + // 4. R = kG + // 5. Repeat from step 3 if R + T is odd + // 6. r = (R + T).x (R.x is the x coordinate of the point R + T) + // 7. e = BLAKE-256(r || m) (Ensure r is padded to 32 bytes) + // 8. Repeat from step 3 (with iteration + 1) if e >= n + // 9. s = k - e*d mod n + // 10. Return (r, s) + + // Step 4, + // + // R = kG + var R secp256k1.JacobianPoint + k := *nonce + secp256k1.ScalarBaseMultNonConst(&k, &R) + + // Step 5. + // + // Check if R + T is odd. If it is, we need to try again with a new nonce. + R.ToAffine() + var rPlusT secp256k1.JacobianPoint + secp256k1.AddNonConst(T, &R, &rPlusT) + rPlusT.ToAffine() + if rPlusT.Y.IsOdd() { + return nil, fmt.Errorf("need new nonce") + } + + // Step 6. + // + // r = (R + T).x (R.x is the x coordinate of the point R + T) + r := &rPlusT.X + + // Step 7. + // + // e = BLAKE-256(r + t || m) (Ensure r is padded to 32 bytes) + var commitmentInput [scalarSize * 2]byte + r.PutBytesUnchecked(commitmentInput[0:scalarSize]) + copy(commitmentInput[scalarSize:], hash[:]) + commitment := blake256.Sum256(commitmentInput[:]) + + // Step 8. + // + // Repeat from step 1 (with iteration + 1) if e >= N + var e secp256k1.ModNScalar + if overflow := e.SetBytes(&commitment); overflow != 0 { + k.Zero() + str := "hash of (R || m) too big" + return nil, errors.New(str) + } + + // Step 9. + // + // s = k - e*d mod n + s := new(secp256k1.ModNScalar).Mul2(&e, privKey).Negate().Add(&k) + k.Zero() + + // Step 10. + // + // Return (r, s, T) + affineT := new(secp256k1.JacobianPoint) + affineT.Set(T) + affineT.ToAffine() + return &AdaptorSignature{ + r: *r, + s: *s, + t: *affineT, + pubKeyTweak: true}, nil +} + +// zeroArray zeroes the memory of a scalar array. +func zeroArray(a *[scalarSize]byte) { + for i := 0; i < scalarSize; i++ { + a[i] = 0x00 + } +} + +// PublicKeyTweakedAdaptorSig creates a public key tweaked adaptor signature. +// This is created by a party which does not know the hidden value, but knows +// the point on the secp256k1 curve derived by multiplying the hidden value by +// the generator point. The party that knows the hidden value can use it to +// create a valid signature from the adaptor signature. Then, the valid +// signature can be combined with the adaptor signature to reveal the hidden +// value. +func PublicKeyTweakedAdaptorSig(privKey *secp256k1.PrivateKey, hash []byte, T *secp256k1.JacobianPoint) (*AdaptorSignature, error) { + // The algorithm for producing an encrypted EC-Schnorr-DCRv0 is as follows: + // The deviations from the original algorithm are in step 5 and 6. + // + // G = curve generator + // n = curve order + // d = private key + // m = message + // r, s = signature + // t = hidden value + // T = t * G + // + // 1. Fail if m is not 32 bytes + // 2. Fail if d = 0 or d >= n + // 3. Use RFC6979 to generate a deterministic nonce k in [1, n-1] + // parameterized by the private key, message being signed, extra data + // that identifies the scheme, and an iteration count + // 4. R = kG + // 5. Repeat from step 3 if R + T is odd + // 6. r = (R + T).x (R.x is the x coordinate of the point R + T) + // 7. e = BLAKE-256(r || m) (Ensure r is padded to 32 bytes) + // 8. Repeat from step 3 (with iteration + 1) if e >= n + // 9. s = k - e*d mod n + // 10. Return (r, s) + + // Step 1. + // + // Fail if m is not 32 bytes + if len(hash) != scalarSize { + str := fmt.Sprintf("wrong size for message hash (got %v, want %v)", + len(hash), scalarSize) + return nil, errors.New(str) + } + + // Step 2. + // + // Fail if d = 0 or d >= n + privKeyScalar := &privKey.Key + if privKeyScalar.IsZero() { + str := "private key is zero" + return nil, errors.New(str) + } + + var privKeyBytes [scalarSize]byte + privKeyScalar.PutBytes(&privKeyBytes) + defer zeroArray(&privKeyBytes) + for iteration := uint32(0); ; iteration++ { + // Step 3. + // + // Use RFC6979 to generate a deterministic nonce k in [1, n-1] + // parameterized by the private key, message being signed, extra data + // that identifies the scheme, and an iteration count + k := secp256k1.NonceRFC6979(privKeyBytes[:], hash, rfc6979ExtraDataV0[:], + nil, iteration) + + // Steps 4-10. + sig, err := schnorrEncryptedSign(privKeyScalar, k, hash, T) + k.Zero() + if err != nil { + // Try again with a new nonce. + continue + } + + return sig, nil + } +} + +func parseSig(sig *schnorr.Signature) (r *secp256k1.FieldVal, s *secp256k1.ModNScalar) { + sigB := sig.Serialize() + r, s = new(secp256k1.FieldVal), new(secp256k1.ModNScalar) + r.SetBytes((*[32]byte)(sigB[0:32])) + s.SetBytes((*[32]byte)(sigB[32:64])) + return r, s +} + +// PrivateKeyTweakedAdaptorSig creates a private key tweaked adaptor signature. +// This is created by a party which knows the hidden value. +func PrivateKeyTweakedAdaptorSig(sig *schnorr.Signature, pubKey *secp256k1.PublicKey, t *secp256k1.ModNScalar) *AdaptorSignature { + T := new(secp256k1.JacobianPoint) + secp256k1.ScalarBaseMultNonConst(t, T) + T.ToAffine() + + r, s := parseSig(sig) + tweakedS := new(secp256k1.ModNScalar).Add2(s, t) + + return &AdaptorSignature{ + r: *r, + s: *tweakedS, + t: *T, + } +} diff --git a/internal/adaptorsigs/adaptor_test.go b/internal/adaptorsigs/adaptor_test.go new file mode 100644 index 0000000000..23354f1b23 --- /dev/null +++ b/internal/adaptorsigs/adaptor_test.go @@ -0,0 +1,131 @@ +package adaptorsigs + +import ( + "math/rand" + "testing" + "time" + + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" +) + +func TestAdaptorSignatureRandom(t *testing.T) { + seed := time.Now().Unix() + rng := rand.New(rand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + for i := 0; i < 100; i++ { + // Generate two private keys + var pkBuf1, pkBuf2 [32]byte + if _, err := rng.Read(pkBuf1[:]); err != nil { + t.Fatalf("failed to read random private key: %v", err) + } + if _, err := rng.Read(pkBuf2[:]); 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 + if _, err := rng.Read(hash1[:]); err != nil { + t.Fatalf("failed to read random hash: %v", err) + } + if _, err := rng.Read(hash2[:]); err != nil { + t.Fatalf("failed to read random hash: %v", err) + } + + // Generate random signature tweak + var tBuf [32]byte + if _, err := rng.Read(tBuf[:]); err != nil { + t.Fatalf("failed to read random private key: %v", err) + } + var tweak secp256k1.ModNScalar + tweak.SetBytes(&tBuf) + + // Sign hash1 with private key 1 + sig, err := schnorr.Sign(privKey1, hash1[:]) + if err != nil { + t.Fatalf("Sign error: %v", err) + } + + // The owner of priv key 1 knows the tweak. Sends a priv key tweaked adaptor sig + // to the owner of priv key 2. + adaptorSigPrivKeyTweak := PrivateKeyTweakedAdaptorSig(sig, privKey1.PubKey(), &tweak) + err = adaptorSigPrivKeyTweak.Verify(hash1[:], privKey1.PubKey()) + if err != nil { + t.Fatalf("verify error: %v", err) + } + + // The owner of privKey2 creates a public key tweaked adaptor sig using + // tweak * G, and sends it to the owner of privKey1. + adaptorSigPubKeyTweak, err := PublicKeyTweakedAdaptorSig(privKey2, hash2[:], adaptorSigPrivKeyTweak.PublicTweak()) + if err != nil { + t.Fatalf("PublicKeyTweakedAdaptorSig error: %v", err) + } + + // The owner of privKey1 knows the tweak, so they can decrypt the + // public key tweaked adaptor sig. + decryptedSig, err := adaptorSigPubKeyTweak.Decrypt(&tweak) + if err != nil { + t.Fatal(err) + } + + // Using the decrypted version of their sig, which has been made public, + // the owner of privKey2 can recover the tweak. + recoveredTweak, err := adaptorSigPubKeyTweak.RecoverTweak(decryptedSig) + if err != nil { + t.Fatal(err) + } + if !recoveredTweak.Equals(&tweak) { + t.Fatalf("original tweak %v != recovered %v", tweak, recoveredTweak) + } + + // Using the recovered tweak, the original priv key tweaked adaptor sig + // can be decrypted. + decryptedOriginalSig, err := adaptorSigPrivKeyTweak.Decrypt(&tweak) + if err != nil { + t.Fatal(err) + } + if valid := decryptedOriginalSig.Verify(hash1[:], privKey1.PubKey()); !valid { + t.Fatal("decrypted original sig is invalid") + } + } +} + +func RandomBytes(len int) []byte { + bytes := make([]byte, len) + _, err := rand.Read(bytes) + if err != nil { + panic("error reading random bytes: " + err.Error()) + } + return bytes +} + +func TestAdaptorSigParsing(t *testing.T) { + adaptor := &AdaptorSignature{} + adaptor.r.SetByteSlice(RandomBytes(32)) + adaptor.s.SetByteSlice(RandomBytes(32)) + adaptor.pubKeyTweak = true + + var tweak secp256k1.JacobianPoint + secp256k1.ScalarBaseMultNonConst(&adaptor.s, &tweak) + + serialized := adaptor.Serialize() + + adaptor2, err := ParseAdaptorSignature(serialized) + if err != nil { + t.Fatal(err) + } + + if !adaptor2.r.Equals(&adaptor.r) { + t.Fatal("r mismatch") + } +} diff --git a/internal/adaptorsigs/dcr/dcr.go b/internal/adaptorsigs/dcr/dcr.go index 2471034a75..e7c2ff0f73 100644 --- a/internal/adaptorsigs/dcr/dcr.go +++ b/internal/adaptorsigs/dcr/dcr.go @@ -5,27 +5,30 @@ import "github.com/decred/dcrd/txscript/v4" func LockRefundTxScript(kal, kaf []byte, locktime int64) ([]byte, error) { return txscript.NewScriptBuilder(). AddOp(txscript.OP_IF). - AddOp(txscript.OP_2). AddData(kal). + AddOp(txscript.OP_2). + AddOp(txscript.OP_CHECKSIGALTVERIFY). AddData(kaf). AddOp(txscript.OP_2). - AddOp(txscript.OP_CHECKMULTISIG). + AddOp(txscript.OP_CHECKSIGALT). AddOp(txscript.OP_ELSE). AddInt64(locktime). AddOp(txscript.OP_CHECKSEQUENCEVERIFY). AddOp(txscript.OP_DROP). AddData(kaf). - AddOp(txscript.OP_CHECKSIG). + AddOp(txscript.OP_2). + AddOp(txscript.OP_CHECKSIGALT). AddOp(txscript.OP_ENDIF). Script() } func LockTxScript(kal, kaf []byte) ([]byte, error) { return txscript.NewScriptBuilder(). - AddOp(txscript.OP_2). AddData(kal). + AddOp(txscript.OP_2). + AddOp(txscript.OP_CHECKSIGALTVERIFY). AddData(kaf). AddOp(txscript.OP_2). - AddOp(txscript.OP_CHECKMULTISIG). + AddOp(txscript.OP_CHECKSIGALT). Script() } diff --git a/internal/adaptorsigs/dleq.go b/internal/adaptorsigs/dleq.go new file mode 100644 index 0000000000..6847deab08 --- /dev/null +++ b/internal/adaptorsigs/dleq.go @@ -0,0 +1,113 @@ +package adaptorsigs + +import ( + "bytes" + "fmt" + + "decred.org/dcrdex/dex/utils" + filipEdwards "filippo.io/edwards25519" + "filippo.io/edwards25519/field" + "github.com/athanorlabs/go-dleq" + "github.com/athanorlabs/go-dleq/ed25519" + dleqEdwards "github.com/athanorlabs/go-dleq/ed25519" + "github.com/athanorlabs/go-dleq/secp256k1" + dleqSecp "github.com/athanorlabs/go-dleq/secp256k1" + dcrEdwards "github.com/decred/dcrd/dcrec/edwards/v2" + dcrSecp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +// ProveDLEQ generates a proof that the public keys generated from the provided +// secret on the secp256k1 and edwards25519 curves are derived from the same +// secret. +func ProveDLEQ(secret []byte) ([]byte, error) { + if len(secret) != 32 { + return nil, fmt.Errorf("secret must be 32 bytes") + } + + secretCopy := make([]byte, len(secret)) + copy(secretCopy, secret) + utils.ReverseSlice(secretCopy) + secretB := [32]byte{} + copy(secretB[:], secretCopy) + + proof, err := dleq.NewProof(ed25519.NewCurve(), secp256k1.NewCurve(), secretB) + if err != nil { + return nil, err + } + + return proof.Serialize(), nil +} + +// edwardsPointsEqual checks equality of edwards curve points in the dcrec +// and go-dleq libraries. +func edwardsPointsEqual(dcrPK *dcrEdwards.PublicKey, dleqPK *dleqEdwards.PointImpl) bool { + xB := dcrPK.GetX().Bytes() + yB := dcrPK.GetY().Bytes() + utils.ReverseSlice(xB) + utils.ReverseSlice(yB) + + x := new(field.Element) + y := new(field.Element) + z := new(field.Element) + t := new(field.Element) + + x.SetBytes(xB) + y.SetBytes(yB) + z.One() + t.Multiply(x, y) + + point := filipEdwards.NewIdentityPoint() + point.SetExtendedCoordinates(x, y, z, t) + + expDLEQ := dleqEdwards.NewPoint(point) + return dleqPK.Equals(expDLEQ) +} + +// VerifyDLEQ verifies a DLEQ proof that the public keys are generated from the +// same secret. +func VerifyDLEQ(spk *dcrSecp.PublicKey, epk *dcrEdwards.PublicKey, proofB []byte) error { + edwardsCurve := dleqEdwards.NewCurve() + secpCurve := dleqSecp.NewCurve() + + proof := new(dleq.Proof) + if err := proof.Deserialize(edwardsCurve, secpCurve, proofB); err != nil { + return err + } + if err := proof.Verify(edwardsCurve, secpCurve); err != nil { + return err + } + + edwardsPoint, ok := proof.CommitmentA.(*dleqEdwards.PointImpl) + if !ok { + return fmt.Errorf("expected ed25519.Point, got %T", proof.CommitmentA) + } + if !edwardsPointsEqual(epk, edwardsPoint) { + return fmt.Errorf("ed25519 points do not match") + } + + secpPoint, ok := proof.CommitmentB.(*dleqSecp.PointImpl) + if !ok { + return fmt.Errorf("expected secp256k1.Point, got %T", proof.CommitmentB) + } + if !bytes.Equal(secpPoint.Encode(), spk.SerializeCompressed()) { + return fmt.Errorf("secp256k1 points do not match") + } + + return nil +} + +// ExtractSecp256k1PubKeyFromProof extracts the secp256k1 public key from a +// DLEQ proof. +func ExtractSecp256k1PubKeyFromProof(proofB []byte) (*dcrSecp.PublicKey, error) { + proof := new(dleq.Proof) + if err := proof.Deserialize(dleqEdwards.NewCurve(), dleqSecp.NewCurve(), proofB); err != nil { + return nil, err + } + + secpPoint, ok := proof.CommitmentB.(*dleqSecp.PointImpl) + if !ok { + return nil, fmt.Errorf("expected secp256k1.Point, got %T", proof.CommitmentB) + } + + return dcrSecp.ParsePubKey(secpPoint.Encode()) +} diff --git a/internal/adaptorsigs/dleq_test.go b/internal/adaptorsigs/dleq_test.go new file mode 100644 index 0000000000..eecbdb29a7 --- /dev/null +++ b/internal/adaptorsigs/dleq_test.go @@ -0,0 +1,60 @@ +package adaptorsigs + +import ( + "testing" + + "decred.org/dcrdex/dex/encode" + "github.com/decred/dcrd/dcrec/edwards/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +func TestDleqProof(t *testing.T) { + pubKeysFromSecret := func(secret [32]byte) (*edwards.PublicKey, *secp256k1.PublicKey) { + epk, _, err := edwards.PrivKeyFromScalar(secret[:]) + if err != nil { + t.Fatalf("PrivKeyFromScalar error: %v", err) + } + + scalarSecret := new(secp256k1.ModNScalar) + overflow := scalarSecret.SetBytes(&secret) + if overflow > 0 { + t.Fatalf("overflow: %d", overflow) + } + spk := secp256k1.NewPrivateKey(scalarSecret) + + return epk.PubKey(), spk.PubKey() + } + + var secret [32]byte + copy(secret[1:], encode.RandomBytes(31)) + + epk, spk := pubKeysFromSecret(secret) + proof, err := ProveDLEQ(secret[:]) + if err != nil { + t.Fatalf("ProveDLEQ error: %v", err) + } + err = VerifyDLEQ(spk, epk, proof) + if err != nil { + t.Fatalf("VerifyDLEQ error: %v", err) + } + + secret[31] += 1 + badEpk, badSpk := pubKeysFromSecret(secret) + err = VerifyDLEQ(badSpk, epk, proof) + if err == nil { + t.Fatalf("badSpk should not verify") + } + err = VerifyDLEQ(spk, badEpk, proof) + if err == nil { + t.Fatalf("badEpk should not verify") + } + + extractedSecp, err := ExtractSecp256k1PubKeyFromProof(proof) + if err != nil { + t.Fatalf("ExtractSecp256k1PubKeyFromProof error: %v", err) + } + + if !extractedSecp.IsEqual(spk) { + t.Fatalf("extractedSecp != spk") + } +} diff --git a/internal/cmd/xmrswap/main.go b/internal/cmd/xmrswap/main.go index bbe538bbf4..4cb7c97dd8 100644 --- a/internal/cmd/xmrswap/main.go +++ b/internal/cmd/xmrswap/main.go @@ -1,5 +1,3 @@ -//go:build libsecp256k1 - package main import ( @@ -17,8 +15,8 @@ import ( "decred.org/dcrdex/dex" "decred.org/dcrdex/dex/config" + "decred.org/dcrdex/internal/adaptorsigs" dcradaptor "decred.org/dcrdex/internal/adaptorsigs/dcr" - "decred.org/dcrdex/internal/libsecp256k1" "decred.org/dcrwallet/v4/rpc/client/dcrwallet" dcrwalletjson "decred.org/dcrwallet/v4/rpc/jsonrpc/types" "github.com/agl/ed25519/edwards25519" @@ -27,6 +25,7 @@ import ( "github.com/decred/dcrd/dcrec" "github.com/decred/dcrd/dcrec/edwards/v2" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" "github.com/decred/dcrd/rpcclient/v8" "github.com/decred/dcrd/txscript/v4" "github.com/decred/dcrd/txscript/v4/sign" @@ -88,8 +87,8 @@ type client struct { pkbsf, pkbs *edwards.PublicKey kaf, kal *secp256k1.PrivateKey pkal, pkaf, pkasl, pkbsl *secp256k1.PublicKey - kbsfDleag, kbslDleag [libsecp256k1.ProofLen]byte - lockTxEsig [libsecp256k1.CTLen]byte + kbsfDleag, kbslDleag []byte + lockTxEsig *adaptorsigs.AdaptorSignature lockTx *wire.MsgTx vIn int } @@ -313,10 +312,10 @@ func run(ctx context.Context) error { // generateDleag starts the trade by creating some keys. func (c *client) generateDleag(ctx context.Context) (pkbsf *edwards.PublicKey, kbvf *edwards.PrivateKey, - pkaf *secp256k1.PublicKey, dleag [libsecp256k1.ProofLen]byte, err error) { + pkaf *secp256k1.PublicKey, dleag []byte, err error) { fail := func(err error) (*edwards.PublicKey, *edwards.PrivateKey, - *secp256k1.PublicKey, [libsecp256k1.ProofLen]byte, error) { - return nil, nil, nil, [libsecp256k1.ProofLen]byte{}, err + *secp256k1.PublicKey, []byte, error) { + return nil, nil, nil, nil, err } // This private key is shared with bob and becomes half of the view key. c.kbvf, err = edwards.GeneratePrivateKey() @@ -347,12 +346,12 @@ func (c *client) generateDleag(ctx context.Context) (pkbsf *edwards.PublicKey, k // Share this pubkey with the other party. c.pkaf = c.kaf.PubKey() - c.kbsfDleag, err = libsecp256k1.Ed25519DleagProve(c.kbsf) + c.kbsfDleag, err = adaptorsigs.ProveDLEQ(c.kbsf.Serialize()) if err != nil { return fail(err) } - c.pkasl, err = secp256k1.ParsePubKey(c.kbsfDleag[:33]) + c.pkasl, err = adaptorsigs.ExtractSecp256k1PubKeyFromProof(c.kbsfDleag) if err != nil { return fail(err) } @@ -362,14 +361,15 @@ func (c *client) generateDleag(ctx context.Context) (pkbsf *edwards.PublicKey, k // generateLockTxn creates even more keys and some transactions. func (c *client) generateLockTxn(ctx context.Context, pkbsf *edwards.PublicKey, - kbvf *edwards.PrivateKey, pkaf *secp256k1.PublicKey, kbsfDleag [libsecp256k1.ProofLen]byte) (refundSig, + kbvf *edwards.PrivateKey, pkaf *secp256k1.PublicKey, kbsfDleag []byte) (refundSig, lockRefundTxScript, lockTxScript []byte, refundTx, spendRefundTx *wire.MsgTx, lockTxVout int, - pkbs *edwards.PublicKey, vkbv *edwards.PrivateKey, dleag [libsecp256k1.ProofLen]byte, err error) { - fail := func(err error) ([]byte, []byte, []byte, *wire.MsgTx, *wire.MsgTx, int, *edwards.PublicKey, *edwards.PrivateKey, [libsecp256k1.ProofLen]byte, error) { - return nil, nil, nil, nil, nil, 0, nil, nil, [libsecp256k1.ProofLen]byte{}, err + pkbs *edwards.PublicKey, vkbv *edwards.PrivateKey, dleag []byte, bdcrpk *secp256k1.PublicKey, err error) { + + fail := func(err error) ([]byte, []byte, []byte, *wire.MsgTx, *wire.MsgTx, int, *edwards.PublicKey, *edwards.PrivateKey, []byte, *secp256k1.PublicKey, error) { + return nil, nil, nil, nil, nil, 0, nil, nil, nil, nil, err } c.kbsfDleag = kbsfDleag - c.pkasl, err = secp256k1.ParsePubKey(c.kbsfDleag[:33]) + c.pkasl, err = adaptorsigs.ExtractSecp256k1PubKeyFromProof(c.kbsfDleag) if err != nil { return fail(err) } @@ -487,7 +487,7 @@ func (c *client) generateLockTxn(ctx context.Context, pkbsf *edwards.PublicKey, refundTx.AddTxIn(txIn) // This sig must be shared with Alice. - refundSig, err = sign.RawTxInSignature(refundTx, c.vIn, lockTxScript, txscript.SigHashAll, c.kal.Serialize(), dcrec.STEcdsaSecp256k1) + refundSig, err = sign.RawTxInSignature(refundTx, c.vIn, lockTxScript, txscript.SigHashAll, c.kal.Serialize(), dcrec.STSchnorrSecp256k1) if err != nil { return fail(err) } @@ -514,27 +514,27 @@ func (c *client) generateLockTxn(ctx context.Context, pkbsf *edwards.PublicKey, spendRefundTx.AddTxIn(txIn) spendRefundTx.Version = wire.TxVersionTreasury - c.kbslDleag, err = libsecp256k1.Ed25519DleagProve(c.kbsl) + c.kbslDleag, err = adaptorsigs.ProveDLEQ(c.kbsl.Serialize()) if err != nil { return fail(err) } - c.pkbsl, err = secp256k1.ParsePubKey(c.kbslDleag[:33]) + c.pkbsl, err = adaptorsigs.ExtractSecp256k1PubKeyFromProof(c.kbslDleag) if err != nil { return fail(err) } - return refundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, c.vIn, c.pkbs, c.vkbv, c.kbslDleag, nil + return refundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, c.vIn, c.pkbs, c.vkbv, c.kbslDleag, pkal, nil } // generateRefundSigs signs the refund tx and shares the spendRefund esig that // allows bob to spend the refund tx. -func (c *client) generateRefundSigs(refundTx, spendRefundTx *wire.MsgTx, vIn int, lockTxScript, lockRefundTxScript []byte, dleag [libsecp256k1.ProofLen]byte) (esig [libsecp256k1.CTLen]byte, refundSig []byte, err error) { - fail := func(err error) ([libsecp256k1.CTLen]byte, []byte, error) { - return [libsecp256k1.CTLen]byte{}, nil, err +func (c *client) generateRefundSigs(refundTx, spendRefundTx *wire.MsgTx, vIn int, lockTxScript, lockRefundTxScript []byte, dleag []byte) (esig *adaptorsigs.AdaptorSignature, refundSig []byte, err error) { + fail := func(err error) (*adaptorsigs.AdaptorSignature, []byte, error) { + return nil, nil, err } c.kbslDleag = dleag c.vIn = vIn - c.pkbsl, err = secp256k1.ParsePubKey(c.kbslDleag[:33]) + c.pkbsl, err = adaptorsigs.ExtractSecp256k1PubKeyFromProof(c.kbslDleag) if err != nil { return fail(err) } @@ -546,13 +546,16 @@ func (c *client) generateRefundSigs(refundTx, spendRefundTx *wire.MsgTx, vIn int var h chainhash.Hash copy(h[:], hash) - esig, err = libsecp256k1.EcdsaotvesEncSign(c.kaf, c.pkbsl, h) + + jacobianBobPubKey := new(secp256k1.JacobianPoint) + c.pkbsl.AsJacobian(jacobianBobPubKey) + esig, err = adaptorsigs.PublicKeyTweakedAdaptorSig(c.kaf, h[:], jacobianBobPubKey) if err != nil { return fail(err) } // Share with bob. - refundSig, err = sign.RawTxInSignature(refundTx, c.vIn, lockTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STEcdsaSecp256k1) + refundSig, err = sign.RawTxInSignature(refundTx, c.vIn, lockTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STSchnorrSecp256k1) if err != nil { return fail(err) } @@ -595,6 +598,7 @@ func (c *client) initDcr(ctx context.Context) (spendTx *wire.MsgTx, err error) { if err != nil { return fail(fmt.Errorf("unable to send lock tx: %v", err)) } + return spendTx, nil } @@ -611,7 +615,7 @@ func (c *client) initXmr(ctx context.Context, vkbv *edwards.PrivateKey, pkbs *ed dest := rpc.Destination{ Amount: xmrAmt, - Address: string(sharedAddr), + Address: sharedAddr, } sendReq := rpc.TransferRequest{ Destinations: []rpc.Destination{dest}, @@ -628,43 +632,52 @@ func (c *client) initXmr(ctx context.Context, vkbv *edwards.PrivateKey, pkbs *ed // sendLockTxSig allows Alice to redeem the dcr. If bob does not send this alice // can eventually take his btc. Otherwise bob refunding will reveal his half of // the xmr spend key allowing Alice to refund. -func (c *client) sendLockTxSig(lockTxScript []byte, spendTx *wire.MsgTx) (esig [libsecp256k1.CTLen]byte, err error) { +func (c *client) sendLockTxSig(lockTxScript []byte, spendTx *wire.MsgTx) (esig *adaptorsigs.AdaptorSignature, err error) { hash, err := txscript.CalcSignatureHash(lockTxScript, txscript.SigHashAll, spendTx, 0, nil) if err != nil { - return [libsecp256k1.CTLen]byte{}, err + return nil, err } var h chainhash.Hash copy(h[:], hash) - esig, err = libsecp256k1.EcdsaotvesEncSign(c.kal, c.pkasl, h) + jacobianAlicePubKey := new(secp256k1.JacobianPoint) + c.pkasl.AsJacobian(jacobianAlicePubKey) + esig, err = adaptorsigs.PublicKeyTweakedAdaptorSig(c.kal, h[:], jacobianAlicePubKey) if err != nil { - return [libsecp256k1.CTLen]byte{}, err + return nil, err } + c.lockTxEsig = esig + return esig, nil } // redeemDcr redeems the dcr, revealing a signature that reveals half of the xmr // spend key. -func (c *client) redeemDcr(ctx context.Context, esig [libsecp256k1.CTLen]byte, lockTxScript []byte, spendTx *wire.MsgTx) (kalSig []byte, err error) { +func (c *client) redeemDcr(ctx context.Context, esig *adaptorsigs.AdaptorSignature, lockTxScript []byte, spendTx *wire.MsgTx, bobDCRPK *secp256k1.PublicKey) (kalSig []byte, err error) { kasl := secp256k1.PrivKeyFromBytes(c.kbsf.Serialize()) - kalSig, err = libsecp256k1.EcdsaotvesDecSig(kasl, esig) + + kalSigShnorr, err := esig.Decrypt(&kasl.Key) if err != nil { return nil, err } + kalSig = kalSigShnorr.Serialize() kalSig = append(kalSig, byte(txscript.SigHashAll)) - kafSig, err := sign.RawTxInSignature(spendTx, 0, lockTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STEcdsaSecp256k1) + kafSig, err := sign.RawTxInSignature(spendTx, 0, lockTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STSchnorrSecp256k1) if err != nil { return nil, err } spendSig, err := txscript.NewScriptBuilder(). - AddData(kalSig). AddData(kafSig). + AddData(kalSig). AddData(lockTxScript). Script() + if err != nil { + return nil, err + } spendTx.TxIn[0].SignatureScript = spendSig @@ -672,7 +685,8 @@ func (c *client) redeemDcr(ctx context.Context, esig [libsecp256k1.CTLen]byte, l if err != nil { return nil, err } - fmt.Println(tx) + + fmt.Println("Redeem Tx -", tx) return kalSig, nil } @@ -680,10 +694,16 @@ func (c *client) redeemDcr(ctx context.Context, esig [libsecp256k1.CTLen]byte, l // redeemXmr redeems xmr by creating a new xmr wallet with the complete spend // and view private keys. func (c *client) redeemXmr(ctx context.Context, kalSig []byte) (*rpc.Client, error) { - kaslRecovered, err := libsecp256k1.EcdsaotvesRecEncKey(c.pkasl, c.lockTxEsig, kalSig[:len(kalSig)-1]) + kalSigParsed, err := schnorr.ParseSignature(kalSig[:len(kalSig)-1]) + if err != nil { + return nil, err + } + kaslRecoveredScalar, err := c.lockTxEsig.RecoverTweak(kalSigParsed) if err != nil { return nil, err } + kaslRecoveredBytes := kaslRecoveredScalar.Bytes() + kaslRecovered := secp256k1.PrivKeyFromBytes(kaslRecoveredBytes[:]) kbsfRecovered, _, err := edwards.PrivKeyFromScalar(kaslRecovered.Serialize()) if err != nil { @@ -728,57 +748,78 @@ func (c *client) redeemXmr(ctx context.Context, kalSig []byte) (*rpc.Client, err // startRefund starts the refund and can be done by either party. func (c *client) startRefund(ctx context.Context, kalSig, kafSig, lockTxScript []byte, refundTx *wire.MsgTx) error { refundSig, err := txscript.NewScriptBuilder(). - AddData(kalSig). AddData(kafSig). + AddData(kalSig). AddData(lockTxScript). Script() + if err != nil { + return err + } refundTx.TxIn[0].SignatureScript = refundSig - _, err = c.dcr.SendRawTransaction(ctx, refundTx, false) + tx, err := c.dcr.SendRawTransaction(ctx, refundTx, false) if err != nil { return err } + + fmt.Println("Cancel Tx -", tx) + return nil } // refundDcr returns dcr to bob while revealing his half of the xmr spend key. -func (c *client) refundDcr(ctx context.Context, spendRefundTx *wire.MsgTx, esig [libsecp256k1.CTLen]byte, lockRefundTxScript []byte) (kafSig []byte, err error) { +func (c *client) refundDcr(ctx context.Context, spendRefundTx *wire.MsgTx, esig *adaptorsigs.AdaptorSignature, lockRefundTxScript []byte) (kafSig []byte, err error) { kasf := secp256k1.PrivKeyFromBytes(c.kbsl.Serialize()) - kafSig, err = libsecp256k1.EcdsaotvesDecSig(kasf, esig) + decryptedSig, err := esig.Decrypt(&kasf.Key) if err != nil { return nil, err } + kafSig = decryptedSig.Serialize() kafSig = append(kafSig, byte(txscript.SigHashAll)) - kalSig, err := sign.RawTxInSignature(spendRefundTx, 0, lockRefundTxScript, txscript.SigHashAll, c.kal.Serialize(), dcrec.STEcdsaSecp256k1) + kalSig, err := sign.RawTxInSignature(spendRefundTx, 0, lockRefundTxScript, txscript.SigHashAll, c.kal.Serialize(), dcrec.STSchnorrSecp256k1) if err != nil { return nil, err } + refundSig, err := txscript.NewScriptBuilder(). - AddData(kalSig). AddData(kafSig). + AddData(kalSig). AddOp(txscript.OP_TRUE). AddData(lockRefundTxScript). Script() + if err != nil { + return nil, err + } spendRefundTx.TxIn[0].SignatureScript = refundSig - _, err = c.dcr.SendRawTransaction(ctx, spendRefundTx, false) + tx, err := c.dcr.SendRawTransaction(ctx, spendRefundTx, false) if err != nil { return nil, err } + + fmt.Println("Refund tx -", tx) + // TODO: Confirm refund happened. return kafSig, nil } // refundXmr refunds xmr but cannot happen without the dcr refund happening first. -func (c *client) refundXmr(ctx context.Context, kafSig []byte, esig [libsecp256k1.CTLen]byte) (*rpc.Client, error) { - kbslRecovered, err := libsecp256k1.EcdsaotvesRecEncKey(c.pkbsl, esig, kafSig[:len(kafSig)-1]) +func (c *client) refundXmr(ctx context.Context, kafSig []byte, esig *adaptorsigs.AdaptorSignature) (*rpc.Client, error) { + kafSigParsed, err := schnorr.ParseSignature(kafSig[:len(kafSig)-1]) + if err != nil { + return nil, err + } + kbslRecoveredScalar, err := esig.RecoverTweak(kafSigParsed) if err != nil { return nil, err } + kbslRecoveredBytes := kbslRecoveredScalar.Bytes() + kbslRecovered := secp256k1.PrivKeyFromBytes(kbslRecoveredBytes[:]) + kaslRecovered, _, err := edwards.PrivKeyFromScalar(kbslRecovered.Serialize()) if err != nil { return nil, fmt.Errorf("unable to recover kasl: %v", err) @@ -834,7 +875,7 @@ func (c *client) takeDcr(ctx context.Context, lockRefundTxScript []byte, spendRe } spendRefundTx.TxOut[0] = txOut - kafSig, err := sign.RawTxInSignature(spendRefundTx, 0, lockRefundTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STEcdsaSecp256k1) + kafSig, err := sign.RawTxInSignature(spendRefundTx, 0, lockRefundTxScript, txscript.SigHashAll, c.kaf.Serialize(), dcrec.STSchnorrSecp256k1) if err != nil { return err } @@ -843,6 +884,9 @@ func (c *client) takeDcr(ctx context.Context, lockRefundTxScript []byte, spendRe AddOp(txscript.OP_FALSE). AddData(lockRefundTxScript). Script() + if err != nil { + return err + } spendRefundTx.TxIn[0].SignatureScript = refundSig @@ -890,7 +934,7 @@ func success(ctx context.Context) error { // Bob generates transactions but does not send anything yet. - _, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + _, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, bDCRPK, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) if err != nil { return fmt.Errorf("unalbe to generate lock transactions: %v", err) } @@ -917,20 +961,19 @@ func success(ctx context.Context) error { time.Sleep(time.Second * 5) // Bob sends esig after confirming on chain xmr tx. - bobEsig, err := bob.sendLockTxSig(lockTxScript, spendTx) if err != nil { return err } // Alice redeems using the esig. - kalSig, err := alice.redeemDcr(ctx, bobEsig, lockTxScript, spendTx) + kalSig, err := alice.redeemDcr(ctx, bobEsig, lockTxScript, spendTx, bDCRPK) if err != nil { return err } // Prove that bob can't just sign the spend tx for the signature we need. - ks, err := sign.RawTxInSignature(spendTx, 0, lockTxScript, txscript.SigHashAll, bob.kal.Serialize(), dcrec.STEcdsaSecp256k1) + ks, err := sign.RawTxInSignature(spendTx, 0, lockTxScript, txscript.SigHashAll, bob.kal.Serialize(), dcrec.STSchnorrSecp256k1) if err != nil { return err } @@ -997,7 +1040,7 @@ func aliceBailsBeforeXmrInit(ctx context.Context) error { // Bob generates transactions but does not send anything yet. - bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, _, _, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, _, _, bobDleag, _, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) if err != nil { return fmt.Errorf("unalbe to generate lock transactions: %v", err) } @@ -1010,7 +1053,6 @@ func aliceBailsBeforeXmrInit(ctx context.Context) error { } // Bob initializes the swap with dcr being sent. - _, err = bob.initDcr(ctx) if err != nil { return err @@ -1069,28 +1111,24 @@ func refund(ctx context.Context) error { } // Alice generates dleag. - pkbsf, kbvf, pkaf, aliceDleag, err := alice.generateDleag(ctx) if err != nil { return err } // Bob generates transactions but does not send anything yet. - - bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, _, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) if err != nil { return fmt.Errorf("unalbe to generate lock transactions: %v", err) } // Alice signs a refund script for Bob. - spendRefundESig, aliceRefundSig, err := alice.generateRefundSigs(refundTx, spendRefundTx, vIn, lockTxScript, lockRefundTxScript, bobDleag) if err != nil { return err } // Bob initializes the swap with dcr being sent. - _, err = bob.initDcr(ctx) if err != nil { return err @@ -1134,6 +1172,7 @@ func refund(ctx context.Context) error { if bal.Balance != xmrAmt { return fmt.Errorf("expected refund xmr balance of %d but got %d", xmrAmt, bal.Balance) } + fmt.Printf("new xmr wallet balance\n%+v\n", *bal) return nil @@ -1167,7 +1206,7 @@ func bobBailsAfterXmrInit(ctx context.Context) error { // Bob generates transactions but does not send anything yet. - bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) + bobRefundSig, lockRefundTxScript, lockTxScript, refundTx, spendRefundTx, vIn, pkbs, vkbv, bobDleag, _, err := bob.generateLockTxn(ctx, pkbsf, kbvf, pkaf, aliceDleag) if err != nil { return fmt.Errorf("unalbe to generate lock transactions: %v", err) } diff --git a/internal/libsecp256k1/README.md b/internal/libsecp256k1/README.md deleted file mode 100644 index 5112e5c0f6..0000000000 --- a/internal/libsecp256k1/README.md +++ /dev/null @@ -1,10 +0,0 @@ -### Package libsecp256k1 - -Package libsecp256k1 includes some primative cryptographic functions needed for -working with adaptor signatures that are not currently found in golang. This imports -code from https://github.com/tecnovert/secp256k1 and uses that with cgo. Both -that library and this package are in an experimental stage. - -### Usage - -Run the `build.sh` script. Currently untested on mac and will not work on Windows. diff --git a/internal/libsecp256k1/build.sh b/internal/libsecp256k1/build.sh deleted file mode 100755 index b19343f0c7..0000000000 --- a/internal/libsecp256k1/build.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -rm -fr secp256k1 -git clone https://github.com/tecnovert/secp256k1 -b anonswap_v0.2 - -cd secp256k1 -./autogen.sh -./configure --enable-module-dleag --enable-experimental --enable-module-generator --enable-module-ed25519 --enable-module-recovery --enable-module-ecdsaotves -make -cd .. diff --git a/internal/libsecp256k1/libsecp256k1.go b/internal/libsecp256k1/libsecp256k1.go deleted file mode 100644 index 637435cc79..0000000000 --- a/internal/libsecp256k1/libsecp256k1.go +++ /dev/null @@ -1,149 +0,0 @@ -// This code is available on the terms of the project LICENSE.md file, -// also available online at https://blueoakcouncil.org/license/1.0.0. - -package libsecp256k1 - -/* -#cgo CFLAGS: -g -Wall -#cgo LDFLAGS: -L. -l:secp256k1/.libs/libsecp256k1.a -#include "secp256k1/include/secp256k1_dleag.h" -#include "secp256k1/include/secp256k1_ecdsaotves.h" -#include - -secp256k1_context* _ctx() { - return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); -} -*/ -import "C" -import ( - "errors" - "unsafe" - - "decred.org/dcrdex/dex/encode" - "github.com/decred/dcrd/dcrec/edwards/v2" - "github.com/decred/dcrd/dcrec/secp256k1/v4" -) - -const ( - ProofLen = 48893 - CTLen = 196 - maxSigLen = 72 // The actual size is variable. -) - -// Ed25519DleagProve creates a proof for checking a discrete logarithm is equal -// across the secp256k1 and ed25519 curves. -func Ed25519DleagProve(privKey *edwards.PrivateKey) (proof [ProofLen]byte, err error) { - secpCtx := C._ctx() - defer C.free(unsafe.Pointer(secpCtx)) - nonce := [32]byte{} - copy(nonce[:], encode.RandomBytes(32)) - key := [32]byte{} - copy(key[:], privKey.Serialize()) - n := (*C.uchar)(unsafe.Pointer(&nonce)) - k := (*C.uchar)(unsafe.Pointer(&key)) - nBits := uint64(252) - nb := (*C.ulong)(unsafe.Pointer(&nBits)) - plen := C.ulong(ProofLen) - p := (*C.uchar)(unsafe.Pointer(&proof)) - res := C.secp256k1_ed25519_dleag_prove(secpCtx, p, &plen, k, *nb, n) - if int(res) != 1 { - return [ProofLen]byte{}, errors.New("C.secp256k1_ed25519_dleag_prove exited with error") - } - return proof, nil -} - -// Ed25519DleagVerify verifies that a descrete logarithm is equal across the -// secp256k1 and ed25519 curves. -func Ed25519DleagVerify(proof [ProofLen]byte) bool { - secpCtx := C._ctx() - defer C.free(unsafe.Pointer(secpCtx)) - pl := C.ulong(ProofLen) - p := (*C.uchar)(unsafe.Pointer(&proof)) - res := C.secp256k1_ed25519_dleag_verify(secpCtx, p, pl) - return res == 1 -} - -// EcdsaotvesEncSign signs the hash and returns an encrypted signature. -func EcdsaotvesEncSign(signPriv *secp256k1.PrivateKey, encPub *secp256k1.PublicKey, hash [32]byte) (cyphertext [CTLen]byte, err error) { - secpCtx := C._ctx() - defer C.free(unsafe.Pointer(secpCtx)) - privBytes := [32]byte{} - copy(privBytes[:], signPriv.Serialize()) - priv := (*C.uchar)(unsafe.Pointer(&privBytes)) - pubBytes := [33]byte{} - copy(pubBytes[:], encPub.SerializeCompressed()) - pub := (*C.uchar)(unsafe.Pointer(&pubBytes)) - h := (*C.uchar)(unsafe.Pointer(&hash)) - s := (*C.uchar)(unsafe.Pointer(&cyphertext)) - res := C.ecdsaotves_enc_sign(secpCtx, s, priv, pub, h) - if int(res) != 1 { - return [CTLen]byte{}, errors.New("C.ecdsaotves_enc_sign exited with error") - } - return cyphertext, nil -} - -// EcdsaotvesEncVerify verifies the encrypted signature. -func EcdsaotvesEncVerify(signPub, encPub *secp256k1.PublicKey, hash [32]byte, cyphertext [CTLen]byte) bool { - secpCtx := C._ctx() - defer C.free(unsafe.Pointer(secpCtx)) - signBytes := [33]byte{} - copy(signBytes[:], signPub.SerializeCompressed()) - sp := (*C.uchar)(unsafe.Pointer(&signBytes)) - encBytes := [33]byte{} - copy(encBytes[:], encPub.SerializeCompressed()) - ep := (*C.uchar)(unsafe.Pointer(&encBytes)) - h := (*C.uchar)(unsafe.Pointer(&hash)) - c := (*C.uchar)(unsafe.Pointer(&cyphertext)) - res := C.ecdsaotves_enc_verify(secpCtx, sp, ep, h, c) - return res == 1 -} - -// EcdsaotvesDecSig retrieves the signature. -func EcdsaotvesDecSig(encPriv *secp256k1.PrivateKey, cyphertext [CTLen]byte) ([]byte, error) { - secpCtx := C._ctx() - defer C.free(unsafe.Pointer(secpCtx)) - encBytes := [32]byte{} - copy(encBytes[:], encPriv.Serialize()) - ep := (*C.uchar)(unsafe.Pointer(&encBytes)) - ct := (*C.uchar)(unsafe.Pointer(&cyphertext)) - var sig [maxSigLen]byte - s := (*C.uchar)(unsafe.Pointer(&sig)) - slen := C.ulong(maxSigLen) - res := C.ecdsaotves_dec_sig(secpCtx, s, &slen, ep, ct) - if int(res) != 1 { - return nil, errors.New("C.ecdsaotves_dec_sig exited with error") - } - sigCopy := make([]byte, maxSigLen) - copy(sigCopy, sig[:]) - // Remove trailing zeros. - for i := maxSigLen - 1; i >= 0; i-- { - if sigCopy[i] != 0 { - break - } - sigCopy = sigCopy[:i] - } - return sigCopy, nil -} - -// EcdsaotvesRecEncKey retrieves the encoded private key from signature and -// cyphertext. -func EcdsaotvesRecEncKey(encPub *secp256k1.PublicKey, cyphertext [CTLen]byte, sig []byte) (encPriv *secp256k1.PrivateKey, err error) { - secpCtx := C._ctx() - defer C.free(unsafe.Pointer(secpCtx)) - pubBytes := [33]byte{} - copy(pubBytes[:], encPub.SerializeCompressed()) - ep := (*C.uchar)(unsafe.Pointer(&pubBytes)) - ct := (*C.uchar)(unsafe.Pointer(&cyphertext)) - sigCopy := [maxSigLen]byte{} - copy(sigCopy[:], sig) - s := (*C.uchar)(unsafe.Pointer(&sigCopy)) - varSigLen := len(sig) - slen := C.ulong(varSigLen) - pkBytes := [32]byte{} - pk := (*C.uchar)(unsafe.Pointer(&pkBytes)) - res := C.ecdsaotves_rec_enc_key(secpCtx, pk, ep, ct, s, slen) - if int(res) != 1 { - return nil, errors.New("C.ecdsaotves_rec_enc_key exited with error") - } - return secp256k1.PrivKeyFromBytes(pkBytes[:]), nil -} diff --git a/internal/libsecp256k1/libsecp256k1_test.go b/internal/libsecp256k1/libsecp256k1_test.go deleted file mode 100644 index fd16d6e97f..0000000000 --- a/internal/libsecp256k1/libsecp256k1_test.go +++ /dev/null @@ -1,215 +0,0 @@ -//go:build libsecp256k1 - -package libsecp256k1 - -import ( - "bytes" - "math/rand" - "testing" - - "github.com/decred/dcrd/dcrec/edwards/v2" - "github.com/decred/dcrd/dcrec/secp256k1/v4" -) - -func randBytes(n int) []byte { - b := make([]byte, n) - rand.Read(b) - return b -} - -func TestEd25519DleagProve(t *testing.T) { - tests := []struct { - name string - }{{ - name: "ok", - }} - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - pk, err := edwards.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - sPk := secp256k1.PrivKeyFromBytes(pk.Serialize()) - proof, err := Ed25519DleagProve(pk) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(sPk.PubKey().SerializeCompressed(), proof[:33]) { - t.Fatal("first 33 bytes of proof not equal to secp256k1 pubkey") - } - }) - } -} - -func TestEd25519DleagVerify(t *testing.T) { - pk, err := edwards.GeneratePrivateKey() - if err != nil { - panic(err) - } - proof, err := Ed25519DleagProve(pk) - if err != nil { - panic(err) - } - tests := []struct { - name string - proof [ProofLen]byte - ok bool - }{{ - name: "ok", - proof: proof, - ok: true, - }, { - name: "bad proof", - proof: func() (p [ProofLen]byte) { - copy(p[:], proof[:]) - p[0] ^= p[0] - return p - }(), - }} - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ok := Ed25519DleagVerify(test.proof) - if ok != test.ok { - t.Fatalf("want %v but got %v", test.ok, ok) - } - }) - } -} - -func TestEcdsaotvesEncSign(t *testing.T) { - tests := []struct { - name string - }{{ - name: "ok", - }} - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - signPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - encPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - h := randBytes(32) - var hash [32]byte - copy(hash[:], h) - _, err = EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) - if err != nil { - t.Fatal(err) - } - }) - } -} - -func TestEcdsaotvesEncVerify(t *testing.T) { - signPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - encPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - h := randBytes(32) - var hash [32]byte - copy(hash[:], h) - ct, err := EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) - if err != nil { - t.Fatal(err) - } - tests := []struct { - name string - ok bool - ct [196]byte - }{{ - name: "ok", - ct: ct, - ok: true, - }, { - name: "bad sig", - ct: func() (c [CTLen]byte) { - copy(c[:], ct[:]) - c[0] ^= c[0] - return c - }(), - }} - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ok := EcdsaotvesEncVerify(signPk.PubKey(), encPk.PubKey(), hash, test.ct) - if ok != test.ok { - t.Fatalf("want %v but got %v", test.ok, ok) - } - }) - } -} - -func TestEcdsaotvesDecSig(t *testing.T) { - signPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - encPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - h := randBytes(32) - var hash [32]byte - copy(hash[:], h) - ct, err := EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) - if err != nil { - t.Fatal(err) - } - tests := []struct { - name string - }{{ - name: "ok", - }} - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - _, err := EcdsaotvesDecSig(encPk, ct) - if err != nil { - t.Fatal(err) - } - }) - } -} - -func TestEcdsaotvesRecEncKey(t *testing.T) { - signPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - encPk, err := secp256k1.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - } - h := randBytes(32) - var hash [32]byte - copy(hash[:], h) - ct, err := EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) - if err != nil { - t.Fatal(err) - } - sig, err := EcdsaotvesDecSig(encPk, ct) - if err != nil { - t.Fatal(err) - } - tests := []struct { - name string - }{{ - name: "ok", - }} - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - pk, err := EcdsaotvesRecEncKey(encPk.PubKey(), ct, sig) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(pk.Serialize(), encPk.Serialize()) { - t.Fatal("private keys not equal") - } - }) - } -} diff --git a/internal/libsecp256k1/secp256k1 b/internal/libsecp256k1/secp256k1 deleted file mode 160000 index e3ebcd782a..0000000000 --- a/internal/libsecp256k1/secp256k1 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e3ebcd782a604f228784b10c50ffa099d9796720 diff --git a/run_tests.sh b/run_tests.sh index 1ec7227cab..ffd79def1c 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -9,10 +9,6 @@ echo "Go version: $GV" # Ensure html templates pass localization. go generate -x ./client/webserver/site # no -write -cd ./internal/libsecp256k1 -./build.sh -go test -race -tags libsecp256k1 - cd "$dir" # list of all modules to test