diff --git a/go.mod b/go.mod index c4bab877fe..cb7472d323 100644 --- a/go.mod +++ b/go.mod @@ -78,12 +78,14 @@ require ( require ( decred.org/cspp/v2 v2.2.0 // indirect + filippo.io/edwards25519 v1.0.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/aead/siphash v1.0.1 // indirect + github.com/athanorlabs/go-dleq v0.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 // indirect 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..1522efc3a9 --- /dev/null +++ b/internal/adaptorsigs/adaptor.go @@ -0,0 +1,530 @@ +package adaptorsigs + +import ( + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +// 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 BIP-340 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 SHA-256([]byte("BIP-340")). + rfc6979ExtraDataV0 = [32]uint8{ + 0xa3, 0xeb, 0x4c, 0x18, 0x2f, 0xae, 0x7e, 0xf4, + 0xe8, 0x10, 0xc6, 0xee, 0x13, 0xb0, 0xe9, 0x26, + 0x68, 0x6d, 0x71, 0xe8, 0x7f, 0x39, 0x4f, 0x79, + 0x9c, 0x00, 0xa5, 0x21, 0x03, 0xcb, 0x4e, 0x17, + } +) + +// 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 btcec.FieldVal + s btcec.ModNScalar + // t will always be in affine coordinates. + t btcec.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, pubKeyB []byte) error { + // The algorithm for producing a BIP-340 signature is as follows: + // This deviates from the original algorithm in step 6. + // + // 1. Fail if m is not 32 bytes + // 2. P = lift_x(int(pk)). + // 3. r = int(sig[0:32]); fail is r >= p. + // 4. s = int(sig[32:64]); fail if s >= n. + // 5. e = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || M)) mod n. + // 6. R = s*G - e*P - T + // 7. Fail if is_infinite(R) + // 8. Fail if not hash_even_y(R) + // 9. Fail is x(R) != r. + // 10. Return success iff not failure occured before reachign this + // point. + + // 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. + // + // P = lift_x(int(pk)) + // + // Fail if P is not a point on the curve + pubKey, err := schnorr.ParsePubKey(pubKeyB) + if err != nil { + return err + } + 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 = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || M)) mod n. + var rBytes [32]byte + sig.r.PutBytesUnchecked(rBytes[:]) + pBytes := schnorr.SerializePubKey(pubKey) + commitment := chainhash.TaggedHash( + chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash, + ) + + var e btcec.ModNScalar + e.SetBytes((*[32]byte)(commitment)) + + // Negate e here so we can use AddNonConst below to subtract the s*G + // point from e*P. + e.Negate() + + // Step 6. + // + // R = s*G - e*P - T + var P, R, sG, eP, encryptedR btcec.JacobianPoint + pubKey.AsJacobian(&P) + btcec.ScalarBaseMultNonConst(&sig.s, &sG) + btcec.ScalarMultNonConst(&e, &P, &eP) + btcec.AddNonConst(&sG, &eP, &R) + tInv := sig.t + tInv.Y.Negate(1) + secp256k1.AddNonConst(&R, &tInv, &encryptedR) + + // Step 7. + // + // 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 8. + // + // 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 R y-value is odd" + return errors.New(str) + } + + // Step 9. + // + // 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) + } + + // Step 10. + // + // Return success iff not failure occurred before reaching this + return nil +} + +// Verify checks that the adaptor signature will result in a valid signature +// when decrypted with the tweak. +func (sig *AdaptorSignature) Verify(hash []byte, pubKey *secp256k1.PublicKey) error { + if sig.pubKeyTweak { + return fmt.Errorf("only private key tweaked adaptors can be verified") + } + pubKeyBytes := schnorr.SerializePubKey(pubKey) + return schnorrAdaptorVerify(sig, hash, pubKeyBytes) +} + +// 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(decryptedSig *schnorr.Signature) (*secp256k1.ModNScalar, error) { + if !sig.pubKeyTweak { + return nil, fmt.Errorf("only public key tweaked sigs can be recovered") + } + + _, s := parseSig(decryptedSig) + + 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 *secp.ModNScalar, hash []byte, pubKey *btcec.PublicKey, T *secp256k1.JacobianPoint) (*AdaptorSignature, error) { + // The algorithm for producing an public key tweaked BIP-340 adaptor + // signature is as follows: + // This deviates from the original algorithm in steps 11 and 12. + // + // G = curve generator + // n = curve order + // d = private key + // m = message + // a = input randmoness + // r, s = signature + // + // 1. d' = int(d) + // 2. Fail if m is not 32 bytes + // 3. Fail if d = 0 or d >= n + // 4. P = d'*G + // 5. Negate d if P.y is odd + // 6. t = bytes(d) xor tagged_hash("BIP0340/aux", t || bytes(P) || m) + // 7. rand = tagged_hash("BIP0340/nonce", a) + // 8. k' = int(rand) mod n + // 9. Fail if k' = 0 + // 10. R = 'k*G + // 11. Check if R + T is odd. If it is, we need to try again with a new nonce. + // 12. e = tagged_hash("BIP0340/challenge", bytes(R + T) || bytes(P) || m) mod n + // 13. sig = bytes(R) || bytes((k + e*d)) mod n + // 14. If Verify(bytes(P), m, sig) fails, abort. + // 15. return sig. + // + // Note that the set of functional options passed in may modify the + // above algorithm. Namely if CustomNonce is used, then steps 6-8 are + // replaced with a process that generates the nonce using rfc6979. If + // FastSign is passed, then we skip set 14. + + // + // Step 10. + // + // R = kG + var R btcec.JacobianPoint + k := *nonce + btcec.ScalarBaseMultNonConst(&k, &R) + + // Step 11. + // + // 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") + } + + r := &rPlusT.X + + // Step 12. + // + // e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || m) mod n + var rBytes [32]byte + r.PutBytesUnchecked(rBytes[:]) + pBytes := schnorr.SerializePubKey(pubKey) + + commitment := chainhash.TaggedHash( + chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash, + ) + + var e btcec.ModNScalar + if overflow := e.SetBytes((*[32]byte)(commitment)); overflow != 0 { + k.Zero() + str := "hash of (r || P || m) too big" + return nil, errors.New(str) + } + + // Step 13. + // + // s = k + e*d mod n + s := new(btcec.ModNScalar).Mul2(&e, privKey).Add(&k) + k.Zero() + + // Step 15. + // + // 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 *btcec.PrivateKey, hash []byte, T *secp256k1.JacobianPoint) (*AdaptorSignature, error) { + // The algorithm for producing an public key tweaked BIP-340 adaptor + // signature is as follows: + // This deviates from the original algorithm in steps 11 and 12. + // + // G = curve generator + // n = curve order + // d = private key + // m = message + // a = input randomness + // r, s = signature + // + // 1. d' = int(d) + // 2. Fail if m is not 32 bytes + // 3. Fail if d = 0 or d >= n + // 4. P = d'*G + // 5. Negate d if P.y is odd + // 6. t = bytes(d) xor tagged_hash("BIP0340/aux", t || bytes(P) || m) + // 7. rand = tagged_hash("BIP0340/nonce", a) + // 8. k' = int(rand) mod n + // 9. Fail if k' = 0 + // 10. R = 'k*G + // 11. Check if R + T is odd. If it is, we need to try again with a new nonce. + // 12. e = tagged_hash("BIP0340/challenge", bytes(R + T) || bytes(P) || m) mod n + // 13. sig = bytes(R) || bytes((k + e*d)) mod n + // 14. If Verify(bytes(P), m, sig) fails, abort. + // 15. return sig. + + // Step 1. + // + // d' = int(d) + var privKeyScalar btcec.ModNScalar + privKeyScalar.Set(&privKey.Key) + + // Step 2. + // + // 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 3. + // + // Fail if d = 0 or d >= n + if privKeyScalar.IsZero() { + str := "private key is zero" + return nil, errors.New(str) + } + + // Step 4. + // + // P = 'd*G + pub := privKey.PubKey() + + // Step 5. + // + // Negate d if P.y is odd. + pubKeyBytes := pub.SerializeCompressed() + if pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd { + privKeyScalar.Negate() + } + + var privKeyBytes [scalarSize]byte + privKeyScalar.PutBytes(&privKeyBytes) + defer zeroArray(&privKeyBytes) + for iteration := uint32(0); ; iteration++ { + // Step 6-9. + // + // 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 := btcec.NonceRFC6979( + privKeyBytes[:], hash, rfc6979ExtraDataV0[:], nil, iteration, + ) + + // Steps 10-15. + sig, err := schnorrEncryptedSign(&privKeyScalar, k, hash, pub, 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 *btcec.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..830c3d9454 --- /dev/null +++ b/internal/adaptorsigs/adaptor_test.go @@ -0,0 +1,133 @@ +package adaptorsigs + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +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 { + fmt.Println(i) + 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/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..e9c1260a38 100644 --- a/internal/cmd/xmrswap/main.go +++ b/internal/cmd/xmrswap/main.go @@ -1,5 +1,3 @@ -//go:build libsecp256k1 - package main import ( @@ -17,11 +15,12 @@ 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" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrec" @@ -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,14 @@ 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, err error) { + fail := func(err error) ([]byte, []byte, []byte, *wire.MsgTx, *wire.MsgTx, int, *edwards.PublicKey, *edwards.PrivateKey, []byte, error) { + return nil, nil, nil, nil, nil, 0, 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) } @@ -514,11 +513,11 @@ 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) } @@ -528,13 +527,13 @@ func (c *client) generateLockTxn(ctx context.Context, pkbsf *edwards.PublicKey, // 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,7 +545,10 @@ 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) } @@ -628,31 +630,37 @@ 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) (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) @@ -680,10 +688,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 { @@ -743,12 +757,13 @@ func (c *client) startRefund(ctx context.Context, kalSig, kafSig, lockTxScript [ } // 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) @@ -773,12 +788,19 @@ func (c *client) refundDcr(ctx context.Context, spendRefundTx *wire.MsgTx, esig } // 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) @@ -1069,28 +1091,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) 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 +1152,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 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