diff --git a/cmd/btcatomicswap/adaptor/adaptor.go b/cmd/btcatomicswap/adaptor/adaptor.go new file mode 100644 index 0000000..0751928 --- /dev/null +++ b/cmd/btcatomicswap/adaptor/adaptor.go @@ -0,0 +1,532 @@ +package adaptor + +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 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.ToAffine() + 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.SetByteSlice(b[0:32]); overflow { + str := "invalid signature: r >= field prime" + return nil, errors.New(str) + } + + var s secp256k1.ModNScalar + if overflow := s.SetByteSlice(b[32:64]); overflow { + str := "invalid signature: s >= group order" + return nil, errors.New(str) + } + + var t secp256k1.JacobianPoint + if overflow := t.X.SetByteSlice(b[64:96]); overflow { + str := "invalid signature: t.x >= field prime" + return nil, errors.New(str) + } + + if overflow := t.Y.SetByteSlice(b[96:128]); overflow { + str := "invalid signature: t.y >= field prime" + return nil, errors.New(str) + } + + t.Z.SetInt(1) + + var pubKeyTweak bool + if b[128] == byte(1) { + pubKeyTweak = true + } + + 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, hash []byte) (*schnorr.Signature, error) { + var expectedT secp256k1.JacobianPoint + secp256k1.ScalarBaseMultNonConst(tweak, &expectedT) + expectedT.ToAffine() + sig.t.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).Add(&sig.s).Negate().Add(s) + + // Verify the recovered tweak + var expectedT secp256k1.JacobianPoint + secp256k1.ScalarBaseMultNonConst(t, &expectedT) + expectedT.ToAffine() + sig.t.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 10. + // + // Return (r, s, T) + return &AdaptorSignature{ + r: *r, + s: *s, + t: *T, + 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) (s *secp256k1.ModNScalar, r *secp256k1.FieldVal) { + sigB := sig.Serialize() + r = new(secp256k1.FieldVal) + r.SetByteSlice(sigB[0:32]) + s = new(secp256k1.ModNScalar) + s.SetByteSlice(sigB[32:64]) + return +} + +// 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) + + s, r := parseSig(sig) + tweakedS := new(secp256k1.ModNScalar).Add(s).Add(t) + + return &AdaptorSignature{ + r: *r, + s: *tweakedS, + t: *T, + } +} diff --git a/cmd/btcatomicswap/adaptor/adaptor_test.go b/cmd/btcatomicswap/adaptor/adaptor_test.go new file mode 100644 index 0000000..4e538b7 --- /dev/null +++ b/cmd/btcatomicswap/adaptor/adaptor_test.go @@ -0,0 +1,133 @@ +package adaptor + +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, hash2[:]) + 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, hash1[:]) + 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/cmd/btcatomicswap/go.mod b/cmd/btcatomicswap/go.mod index d2c7040..e13088f 100644 --- a/cmd/btcatomicswap/go.mod +++ b/cmd/btcatomicswap/go.mod @@ -1,12 +1,23 @@ module github.com/decred/atomicswap/cmd/btcatomicswap +go 1.21.3 + +require ( + github.com/btcsuite/btcd v0.23.4 + github.com/btcsuite/btcd/btcec/v2 v2.2.2 + github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 + github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 + golang.org/x/crypto v0.7.0 +) + require ( - github.com/btcsuite/btcd v0.0.0-20181013004428-67e573d211ac + github.com/aead/siphash v1.0.1 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a - github.com/btcsuite/btcwallet v0.0.0-20181017015332-c4dd27e481f9 github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 + github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/kkdai/bstream v1.0.0 // indirect + golang.org/x/sys v0.6.0 // indirect ) diff --git a/cmd/btcatomicswap/go.sum b/cmd/btcatomicswap/go.sum index d7f6a30..1268ca7 100644 --- a/cmd/btcatomicswap/go.sum +++ b/cmd/btcatomicswap/go.sum @@ -1,16 +1,124 @@ -github.com/btcsuite/btcd v0.0.0-20181013004428-67e573d211ac h1:/zx+Hglw2JN/pwVam1Z8cTCTl4pWyrbvOn2oooqCQSs= -github.com/btcsuite/btcd v0.0.0-20181013004428-67e573d211ac/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.4 h1:IzV6qqkfwbItOS/sg/aDfPDsjPP8twrCOE2R93hxMlQ= +github.com/btcsuite/btcd v0.23.4/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.2.2 h1:5uxe5YjoCq+JeOpg0gZSNHuFgeogrocBYxvg6w9sAgc= +github.com/btcsuite/btcd/btcec/v2 v2.2.2/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= -github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.0.0-20181017015332-c4dd27e481f9 h1:YmgBq9XwdnZGBRF2DXq1a4qh/pfduvdQlzaDCjPV/jc= -github.com/btcsuite/btcwallet v0.0.0-20181017015332-c4dd27e481f9/go.mod h1:+q7/nPeXqu8jJ0ah0fcMOlWGZ2GeL2QoL6c7nCFUEVA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= +github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/btcatomicswap/main.go b/cmd/btcatomicswap/main.go index 23ad543..445e327 100644 --- a/cmd/btcatomicswap/main.go +++ b/cmd/btcatomicswap/main.go @@ -9,6 +9,7 @@ import ( "bytes" "crypto/rand" "crypto/sha256" + "encoding/binary" "encoding/hex" "encoding/json" "errors" @@ -20,13 +21,17 @@ import ( "strings" "time" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" rpc "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/wallet/txrules" + "github.com/decred/atomicswap/cmd/btcatomicswap/adaptor" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "golang.org/x/crypto/ripemd160" ) @@ -46,6 +51,7 @@ var ( rpcuserFlag = flagset.String("rpcuser", "", "username for wallet RPC authentication") rpcpassFlag = flagset.String("rpcpass", "", "password for wallet RPC authentication") testnetFlag = flagset.Bool("testnet", false, "use testnet network") + simnetFlag = flagset.Bool("simnet", false, "use simnet network") ) // There are two directions that the atomic swap can be performed, as the @@ -82,6 +88,16 @@ func init() { fmt.Println(" extractsecret ") fmt.Println(" auditcontract ") fmt.Println() + fmt.Println("Private swap commands:") + fmt.Println(" getpubkey") + fmt.Println(" lockfunds ") + fmt.Println(" auditprivatecontract ") + fmt.Println(" unsignedredemption ") + fmt.Println(" initiateadaptor ") + fmt.Println(" verifyadaptor ") + fmt.Println(" participateadaptor ") + fmt.Println(" privateredeem ") + fmt.Println(" extracttweak ") fmt.Println("Flags:") flagset.PrintDefaults() } @@ -129,6 +145,71 @@ type auditContractCmd struct { contractTx *wire.MsgTx } +// The following commands are for private atomic swaps: + +type getPubKeyCmd struct{} + +type lockFundsCmd struct { + cpPubKey *btcec.PublicKey + amount btcutil.Amount + initiator bool +} + +type unsignedRedemptionCmd struct { + cpLockTx *wire.MsgTx + cpRedeemContract []byte + cpRefundContract []byte + cpTxInternalKeyNonce *secp256k1.ModNScalar +} + +type initiateAdaptorCmd struct { + ourRedeemContract []byte + ourRefundContract []byte + ourTxInternalKeyNonce *secp256k1.ModNScalar + ourLockTx *wire.MsgTx + cpUnsignedRedeemTx *wire.MsgTx +} + +type verifyAdaptorCmd struct { + cpRedeemContract []byte + cpRefundContract []byte + cpTxInternalKeyNonce *secp256k1.ModNScalar + cpAdaptorSig *adaptor.AdaptorSignature + cpLockTx *wire.MsgTx + ourUnsignedRedeem *wire.MsgTx +} + +type participateAdaptorCmd struct { + ourRedeemContract []byte + ourRefundContract []byte + ourTxInternalKeyNonce *secp256k1.ModNScalar + ourLockTx *wire.MsgTx + cpUnsignedRedeemTx *wire.MsgTx + cpAdaptor *adaptor.AdaptorSignature +} + +type privateRedeemCmd struct { + cpRedeemContract []byte + cpRefundContract []byte + cpTxInternalKeyNonce *secp256k1.ModNScalar + cpLockTx *wire.MsgTx + cpAdaptor *adaptor.AdaptorSignature + unsignedRedemption *wire.MsgTx + tweak *secp256k1.ModNScalar +} + +type extractTweakCmd struct { + cpRedeemTx *wire.MsgTx + ourAdaptor *adaptor.AdaptorSignature +} + +type auditPrivateContractCmd struct { + redeemContract []byte + refundContract []byte + internalKeyNonce *secp256k1.ModNScalar + lockTx *wire.MsgTx +} + func main() { err, showUsage := run() if err != nil { @@ -174,6 +255,24 @@ func run() (err error, showUsage bool) { cmdArgs = 2 case "auditcontract": cmdArgs = 2 + case "lockfunds": + cmdArgs = 3 + case "unsignedredemption": + cmdArgs = 4 + case "initiateadaptor": + cmdArgs = 5 + case "verifyadaptor": + cmdArgs = 6 + case "participateadaptor": + cmdArgs = 6 + case "privateredeem": + cmdArgs = 7 + case "getpubkey": + cmdArgs = 0 + case "extracttweak": + cmdArgs = 2 + case "auditprivatecontract": + cmdArgs = 4 default: return fmt.Errorf("unknown command %v", args[0]), true } @@ -189,6 +288,9 @@ func run() (err error, showUsage bool) { if *testnetFlag { chainParams = &chaincfg.TestNet3Params } + if *simnetFlag { + chainParams = &chaincfg.RegressionNetParams + } var cmd command switch args[0] { @@ -216,7 +318,6 @@ func run() (err error, showUsage bool) { } cmd = &initiateCmd{cp2Addr: cp2AddrP2PKH, amount: amount} - case "participate": cp1Addr, err := btcutil.DecodeAddress(args[1], chainParams) if err != nil { @@ -329,6 +430,364 @@ func run() (err error, showUsage bool) { } cmd = &auditContractCmd{contract: contract, contractTx: &contractTx} + case "lockfunds": + cpPubKeyBytes, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode counterparty public key: %v", err), true + } + if cpPubKeyBytes[0] != secp256k1.PubKeyFormatCompressedEven { + return fmt.Errorf("counterparty public key must be even"), true + } + cpPubKey, err := btcec.ParsePubKey(cpPubKeyBytes) + if err != nil { + return fmt.Errorf("failed to parse counterparty public key: %v", err), true + } + + amountF64, err := strconv.ParseFloat(args[2], 64) + if err != nil { + return fmt.Errorf("failed to decode amount: %v", err), true + } + amount, err := btcutil.NewAmount(amountF64) + if err != nil { + return err, true + } + + isInitiator, err := strconv.ParseBool(args[3]) + if err != nil { + return fmt.Errorf("failed to decode initiator flag: %v", err), true + } + + cmd = &lockFundsCmd{ + cpPubKey: cpPubKey, + amount: amount, + initiator: isInitiator, + } + + case "unsignedredemption": + redeemContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to redeem contract: %v", err), true + } + + refundContract, err := hex.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("failed to refund contract: %v", err), true + } + + nonceBytes, err := hex.DecodeString(args[3]) + if err != nil { + return err, true + } + internalKeyNonce := new(secp256k1.ModNScalar) + internalKeyNonce.SetByteSlice(nonceBytes) + + txBytes, err := hex.DecodeString(args[4]) + if err != nil { + return fmt.Errorf("failed to decode transaction: %v", err), true + } + var tx wire.MsgTx + err = tx.Deserialize(bytes.NewReader(txBytes)) + if err != nil { + return fmt.Errorf("failed to decode transaction: %v", err), true + } + + cmd = &unsignedRedemptionCmd{ + cpLockTx: &tx, + cpTxInternalKeyNonce: internalKeyNonce, + cpRedeemContract: redeemContract, + cpRefundContract: refundContract, + } + case "initiateadaptor": + ourRedeemContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode our redeem contract: %v", err), true + } + + ourRefundContract, err := hex.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("failed to decode our refund contract: %v", err), true + } + + nonceBytes, err := hex.DecodeString(args[3]) + if err != nil { + return err, true + } + internalKeyNonce := new(secp256k1.ModNScalar) + internalKeyNonce.SetByteSlice(nonceBytes) + + ourLockTxBytes, err := hex.DecodeString(args[4]) + if err != nil { + return fmt.Errorf("failed to decode our lock transaction: %v", err), true + } + var ourLockTx wire.MsgTx + err = ourLockTx.Deserialize(bytes.NewReader(ourLockTxBytes)) + if err != nil { + return fmt.Errorf("failed to deserialize our lock transaction: %v", err), true + } + + cpUnsignedRedeemTxBytes, err := hex.DecodeString(args[5]) + if err != nil { + return fmt.Errorf("failed to decode counterparty unsigned redeem transaction: %v", err), true + } + + var cpUnsignedRedeemTx wire.MsgTx + err = cpUnsignedRedeemTx.Deserialize(bytes.NewReader(cpUnsignedRedeemTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode counterparty unsigned redeem transaction: %v", err), true + } + + cmd = &initiateAdaptorCmd{ + ourRedeemContract: ourRedeemContract, + ourRefundContract: ourRefundContract, + ourTxInternalKeyNonce: internalKeyNonce, + ourLockTx: &ourLockTx, + cpUnsignedRedeemTx: &cpUnsignedRedeemTx, + } + + case "verifyadaptor": + cpRedeemContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode counterparty redeem contract: %v", err), true + } + + cpRefundContract, err := hex.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("failed to decode counterparty refund contract: %v", err), true + } + + nonceBytes, err := hex.DecodeString(args[3]) + if err != nil { + return err, true + } + internalKeyNonce := new(secp256k1.ModNScalar) + internalKeyNonce.SetByteSlice(nonceBytes) + + cpLockTxBytes, err := hex.DecodeString(args[4]) + if err != nil { + return fmt.Errorf("failed to decode counterparty lock transaction: %v", err), true + } + + cpAdaptorSigBytes, err := hex.DecodeString(args[5]) + if err != nil { + return fmt.Errorf("failed to decode counterparty adaptor signature: %v", err), true + } + cpAdaptorSig, err := adaptor.ParseAdaptorSignature(cpAdaptorSigBytes) + if err != nil { + return fmt.Errorf("failed to parse counterparty adaptor signature: %v", err), true + } + + var cpLockTx wire.MsgTx + err = cpLockTx.Deserialize(bytes.NewReader(cpLockTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode counterparty lock transaction: %v", err), true + } + + ourUnsignedRedeemTxBytes, err := hex.DecodeString(args[6]) + if err != nil { + return fmt.Errorf("failed to decode our unsigned redeem transaction: %v", err), true + } + + var ourUnsignedRedeemTx wire.MsgTx + err = ourUnsignedRedeemTx.Deserialize(bytes.NewReader(ourUnsignedRedeemTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode our unsigned redeem transaction: %v", err), true + } + + cmd = &verifyAdaptorCmd{ + cpRedeemContract: cpRedeemContract, + cpRefundContract: cpRefundContract, + cpTxInternalKeyNonce: internalKeyNonce, + cpAdaptorSig: cpAdaptorSig, + cpLockTx: &cpLockTx, + ourUnsignedRedeem: &ourUnsignedRedeemTx, + } + case "participateadaptor": + ourRedeemContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode our redeem contract: %v", err), true + } + + ourRefundContract, err := hex.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("failed to decode our refund contract: %v", err), true + } + + nonceBytes, err := hex.DecodeString(args[3]) + if err != nil { + return err, true + } + internalKeyNonce := new(secp256k1.ModNScalar) + internalKeyNonce.SetByteSlice(nonceBytes) + + ourLockTxBytes, err := hex.DecodeString(args[4]) + if err != nil { + return fmt.Errorf("failed to decode our lock transaction: %v", err), true + } + + var ourLockTx wire.MsgTx + err = ourLockTx.Deserialize(bytes.NewReader(ourLockTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode our lock transaction: %v", err), true + } + + cpUnsignedRedeemTxBytes, err := hex.DecodeString(args[5]) + if err != nil { + return fmt.Errorf("failed to decode counterparty unsigned redeem transaction: %v", err), true + } + + var cpUnsignedRedeemTx wire.MsgTx + err = cpUnsignedRedeemTx.Deserialize(bytes.NewReader(cpUnsignedRedeemTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode counterparty unsigned redeem transaction: %v", err), true + } + + cpAdaptorSigBytes, err := hex.DecodeString(args[6]) + if err != nil { + return fmt.Errorf("failed to decode counterparty adaptor signature: %v", err), true + } + cpAdaptorSig, err := adaptor.ParseAdaptorSignature(cpAdaptorSigBytes) + if err != nil { + return fmt.Errorf("failed to parse counterparty adaptor signature: %v", err), true + } + + cmd = &participateAdaptorCmd{ + ourRedeemContract: ourRedeemContract, + ourRefundContract: ourRefundContract, + ourTxInternalKeyNonce: internalKeyNonce, + ourLockTx: &ourLockTx, + cpUnsignedRedeemTx: &cpUnsignedRedeemTx, + cpAdaptor: cpAdaptorSig, + } + + case "privateredeem": + cpRedeemContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode counterparty redeem contract: %v", err), true + } + + cpRefundContract, err := hex.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("failed to decode counterparty refund contract: %v", err), true + } + + nonceBytes, err := hex.DecodeString(args[3]) + if err != nil { + return err, true + } + internalKeyNonce := new(secp256k1.ModNScalar) + internalKeyNonce.SetByteSlice(nonceBytes) + + cpLockTxBytes, err := hex.DecodeString(args[4]) + if err != nil { + return fmt.Errorf("failed to decode counterparty lock transaction: %v", err), true + } + + var cpLockTx wire.MsgTx + err = cpLockTx.Deserialize(bytes.NewReader(cpLockTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode counterparty lock transaction: %v", err), true + } + + cpAdaptorSigBytes, err := hex.DecodeString(args[5]) + if err != nil { + return fmt.Errorf("failed to decode counterparty adaptor signature: %v", err), true + } + cpAdaptorSig, err := adaptor.ParseAdaptorSignature(cpAdaptorSigBytes) + if err != nil { + return fmt.Errorf("failed to parse counterparty adaptor signature: %v", err), true + } + + unsignedRedemptionTxBytes, err := hex.DecodeString(args[6]) + if err != nil { + return fmt.Errorf("failed to decode unsigned redemption transaction: %v", err), true + } + + var unsignedRedemptionTx wire.MsgTx + err = unsignedRedemptionTx.Deserialize(bytes.NewReader(unsignedRedemptionTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode unsigned redemption transaction: %v", err), true + } + + tweakBytes, err := hex.DecodeString(args[7]) + if err != nil { + return fmt.Errorf("failed to decode tweak: %v", err), true + } + var tweakBuf [32]byte + copy(tweakBuf[:], tweakBytes) + var tweak secp256k1.ModNScalar + tweak.SetBytes(&tweakBuf) + + cmd = &privateRedeemCmd{ + cpRedeemContract: cpRedeemContract, + cpRefundContract: cpRefundContract, + cpTxInternalKeyNonce: internalKeyNonce, + cpLockTx: &cpLockTx, + cpAdaptor: cpAdaptorSig, + unsignedRedemption: &unsignedRedemptionTx, + tweak: &tweak, + } + case "getpubkey": + cmd = &getPubKeyCmd{} + case "extracttweak": + cpRedeemTxBytes, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode counterparty redeem transaction: %v", err), true + } + + var cpRedeemTx wire.MsgTx + err = cpRedeemTx.Deserialize(bytes.NewReader(cpRedeemTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode counterparty redeem transaction: %v", err), true + } + + ourAdaptorSigBytes, err := hex.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("failed to decode our adaptor signature: %v", err), true + } + ourAdaptorSig, err := adaptor.ParseAdaptorSignature(ourAdaptorSigBytes) + if err != nil { + return fmt.Errorf("failed to parse our adaptor signature: %v", err), true + } + + cmd = &extractTweakCmd{ + cpRedeemTx: &cpRedeemTx, + ourAdaptor: ourAdaptorSig, + } + case "auditprivatecontract": + redeemContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode redeem contract: %v", err), true + } + + refundContract, err := hex.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("failed to decode refund contract: %v", err), true + } + + nonceBytes, err := hex.DecodeString(args[3]) + if err != nil { + return err, true + } + internalKeyNonce := new(secp256k1.ModNScalar) + internalKeyNonce.SetByteSlice(nonceBytes) + + lockTxBytes, err := hex.DecodeString(args[4]) + if err != nil { + return fmt.Errorf("failed to decode lock transaction: %v", err), true + } + + var lockTx wire.MsgTx + err = lockTx.Deserialize(bytes.NewReader(lockTxBytes)) + if err != nil { + return fmt.Errorf("failed to decode lock transaction: %v", err), true + } + + cmd = &auditPrivateContractCmd{ + redeemContract: redeemContract, + refundContract: refundContract, + internalKeyNonce: internalKeyNonce, + lockTx: &lockTx, + } } // Offline commands don't need to talk to the wallet. @@ -635,6 +1094,7 @@ func promptPublishTx(c *rpc.Client, tx *wire.MsgTx, name string) error { return fmt.Errorf("sendrawtransaction: %v", err) } fmt.Printf("Published %s transaction (%v)\n", name, txHash) + return nil } } @@ -728,7 +1188,6 @@ func buildContract(c *rpc.Client, args *contractArgs) (*builtContract, error) { func buildRefund(c *rpc.Client, contract []byte, contractTx *wire.MsgTx, feePerKb, minFeePerKb btcutil.Amount) ( refundTx *wire.MsgTx, refundFee btcutil.Amount, err error) { - contractP2SH, err := btcutil.NewAddressScriptHash(contract, chainParams) if err != nil { return nil, 0, err @@ -795,9 +1254,11 @@ func buildRefund(c *rpc.Client, contract []byte, contractTx *wire.MsgTx, feePerK refundTx.TxIn[0].SignatureScript = refundSigScript if verify { + prevOut := contractTx.TxOut[contractOutPoint.Index] + a := txscript.NewCannedPrevOutputFetcher(prevOut.PkScript, prevOut.Value) e, err := txscript.NewEngine(contractTx.TxOut[contractOutPoint.Index].PkScript, refundTx, 0, txscript.StandardVerifyFlags, txscript.NewSigCache(10), - txscript.NewTxSigHashes(refundTx), contractTx.TxOut[contractOutPoint.Index].Value) + txscript.NewTxSigHashes(refundTx, a), contractTx.TxOut[contractOutPoint.Index].Value, a) if err != nil { panic(err) } @@ -808,69 +1269,340 @@ func buildRefund(c *rpc.Client, contract []byte, contractTx *wire.MsgTx, feePerK } return refundTx, refundFee, nil + } -func sha256Hash(x []byte) []byte { - h := sha256.Sum256(x) - return h[:] +type privateContractArgs struct { + us *btcec.PublicKey + them *btcec.PublicKey + amount btcutil.Amount + locktime int64 + internalKey *secp256k1.PublicKey } -func calcFeePerKb(absoluteFee btcutil.Amount, serializeSize int) float64 { - return float64(absoluteFee) / float64(serializeSize) / 1e5 +type builtPrivateContract struct { + redeemContract []byte + refundContract []byte + contractTxHash *chainhash.Hash + contractTx *wire.MsgTx + contractFee btcutil.Amount + refundTx *wire.MsgTx + refundFee btcutil.Amount } -func (cmd *initiateCmd) runCommand(c *rpc.Client) error { - var secret [secretSize]byte - _, err := rand.Read(secret[:]) - if err != nil { - return err - } - secretHash := sha256Hash(secret[:]) +// fakeInternalKey creates a fake internal key using a given nonce. This is required +// becuase only the redeem and refund branches in the script tree can be used to +// spend the contract output. +// The details of this can be found in BIP341: +// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs +func fakeInternalKey(nonce *secp256k1.ModNScalar) *secp256k1.PublicKey { + gx := new(secp256k1.FieldVal) + gx.SetByteSlice(secp256k1.Params().Gx.Bytes()) - // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted - // as a unix time rather than a block height. - locktime := time.Now().Add(48 * time.Hour).Unix() + gy := new(secp256k1.FieldVal) + gy.SetByteSlice(secp256k1.Params().Gy.Bytes()) - b, err := buildContract(c, &contractArgs{ - them: cmd.cp2Addr, - amount: cmd.amount, - locktime: locktime, - secretHash: secretHash, - }) + g := secp256k1.NewPublicKey(gx, gy) + gHash := sha256.Sum256(g.SerializeUncompressed()) + + pubKey, err := secp256k1.ParsePubKey(append([]byte{secp256k1.PubKeyFormatCompressedEven}, gHash[:]...)) if err != nil { - return err + panic(fmt.Sprintf("failed to create fake internal key: %v", err)) } - refundTxHash := b.refundTx.TxHash() - contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize()) - refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize()) + var jacobianPubKey secp256k1.JacobianPoint + pubKey.AsJacobian(&jacobianPubKey) - fmt.Printf("Secret: %x\n", secret) - fmt.Printf("Secret hash: %x\n\n", secretHash) - fmt.Printf("Contract fee: %v (%0.8f BTC/kB)\n", b.contractFee, contractFeePerKb) - fmt.Printf("Refund fee: %v (%0.8f BTC/kB)\n\n", b.refundFee, refundFeePerKb) - fmt.Printf("Contract (%v):\n", b.contractP2SH) - fmt.Printf("%x\n\n", b.contract) - var contractBuf bytes.Buffer - contractBuf.Grow(b.contractTx.SerializeSize()) - b.contractTx.Serialize(&contractBuf) - fmt.Printf("Contract transaction (%v):\n", b.contractTxHash) - fmt.Printf("%x\n\n", contractBuf.Bytes()) - var refundBuf bytes.Buffer - refundBuf.Grow(b.refundTx.SerializeSize()) - b.refundTx.Serialize(&refundBuf) - fmt.Printf("Refund transaction (%v):\n", &refundTxHash) - fmt.Printf("%x\n\n", refundBuf.Bytes()) + var jacobianInternalKey secp256k1.JacobianPoint + secp256k1.ScalarMultNonConst(nonce, &jacobianPubKey, &jacobianInternalKey) + jacobianInternalKey.ToAffine() - return promptPublishTx(c, b.contractTx, "contract") + return secp256k1.NewPublicKey(&jacobianInternalKey.X, &jacobianInternalKey.Y) } -func (cmd *participateCmd) runCommand(c *rpc.Client) error { - // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted - // as a unix time rather than a block height. - locktime := time.Now().Add(24 * time.Hour).Unix() +func buildPrivateContract(c *rpc.Client, args *privateContractArgs) (*builtPrivateContract, error) { + redeemScript, err := privateAtomicSwapRedeemScript(args.us, args.them) + if err != nil { + return nil, err + } - b, err := buildContract(c, &contractArgs{ + refundScript, err := privateAtomicSwapRefundScript(args.us, args.locktime) + if err != nil { + return nil, err + } + + normalLeaf := txscript.NewBaseTapLeaf(redeemScript) + refundLeaf := txscript.NewBaseTapLeaf(refundScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(normalLeaf, refundLeaf) + tapScriptRootHash := tapScriptTree.RootNode.TapHash() + outputKey := txscript.ComputeTaprootOutputKey(args.internalKey, tapScriptRootHash[:]) + pkScript, err := payToTaprootScript(outputKey) + if err != nil { + return nil, err + } + refundControlBlock := tapScriptTree.LeafMerkleProofs[1].ToControlBlock(args.internalKey) + + feePerKb, _, err := getFeePerKb(c) + if err != nil { + return nil, err + } + unsignedContract := wire.NewMsgTx(txVersion) + unsignedContract.AddTxOut(wire.NewTxOut(int64(args.amount), pkScript)) + unsignedContract, contractFee, err := fundRawTransaction(c, unsignedContract, feePerKb) + if err != nil { + return nil, fmt.Errorf("fundrawtransaction: %v", err) + } + + contractTx, complete, err := signRawTransaction(c, unsignedContract) + if err != nil { + return nil, fmt.Errorf("signrawtransaction: %v", err) + } + if !complete { + return nil, errors.New("signrawtransaction: failed to completely sign contract transaction") + } + contractTxHash := contractTx.TxHash() + + refundTx, refundFee, err := buildPrivateRefund(c, pkScript, refundScript, args.locktime, args.us, refundLeaf, &refundControlBlock, contractTx) + if err != nil { + return nil, err + } + + return &builtPrivateContract{ + redeemScript, + refundScript, + &contractTxHash, + contractTx, + contractFee, + refundTx, + refundFee, + }, nil +} + +func getPrivKeyFromPubKey(c *rpc.Client, pubKey *btcec.PublicKey) (*btcec.PrivateKey, error) { + address, err := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), chainParams) + if err != nil { + return nil, err + } + wif, err := c.DumpPrivKey(address) + if err != nil { + return nil, err + } + return wif.PrivKey, nil +} + +func buildPrivateRefund(c *rpc.Client, pkScript, refundScript []byte, lockTime int64, pubKey *secp256k1.PublicKey, + refundLeaf txscript.TapLeaf, controlBlock *txscript.ControlBlock, contractTx *wire.MsgTx) (*wire.MsgTx, btcutil.Amount, error) { + contractOutPoint := wire.OutPoint{Hash: contractTx.TxHash(), Index: ^uint32(0)} + var contractTxOut *wire.TxOut + for i, out := range contractTx.TxOut { + if bytes.Equal(out.PkScript, pkScript) { + contractOutPoint.Index = uint32(i) + contractTxOut = out + break + } + } + if contractOutPoint.Index == ^uint32(0) { + return nil, 0, errors.New("transaction does not contain a contract output") + } + + address, err := getNewAddress(c) + if err != nil { + return nil, 0, fmt.Errorf("error getting new address: %v", err) + } + addr, err := btcutil.DecodeAddress(address, chainParams) + if err != nil { + return nil, 0, err + } + outScript, err := txscript.PayToAddrScript(addr) + if err != nil { + return nil, 0, err + } + + refundTx := wire.NewMsgTx(txVersion) + refundTx.LockTime = uint32(lockTime) + + txIn := wire.NewTxIn(&contractOutPoint, nil, nil) + txIn.Sequence = 0 + refundTx.AddTxIn(txIn) + refundTx.AddTxOut(wire.NewTxOut(0, outScript)) + refundSize := estimatePrivateRefundSerializeSize(refundTx.TxOut) + + feePerKb, minFeePerKb, err := getFeePerKb(c) + if err != nil { + return nil, 0, err + } + fee := txrules.FeeForSerializeSize(feePerKb, refundSize) + refundTx.TxOut[0].Value = contractTxOut.Value - int64(fee) + if txrules.IsDustOutput(refundTx.TxOut[0], minFeePerKb) { + return nil, 0, fmt.Errorf("redeem output value of %v is dust", btcutil.Amount(refundTx.TxOut[0].Value)) + } + + a := txscript.NewMultiPrevOutFetcher(map[wire.OutPoint]*wire.TxOut{ + contractOutPoint: { + Value: contractTxOut.Value, + PkScript: contractTxOut.PkScript, + }, + }) + sigHashes := txscript.NewTxSigHashes(refundTx, a) + + privKey, err := getPrivKeyFromPubKey(c, pubKey) + if err != nil { + return nil, 0, err + } + defer privKey.Zero() + + sig, err := txscript.RawTxInTapscriptSignature( + refundTx, sigHashes, 0, contractTxOut.Value, contractTxOut.PkScript, refundLeaf, + txscript.SigHashDefault, privKey) + if err != nil { + return nil, 0, err + } + + controlBlockB, err := controlBlock.ToBytes() + if err != nil { + return nil, 0, err + } + + refundTx.TxIn[0].Witness = wire.TxWitness{sig, refundScript, controlBlockB} + + if verify { + engine, err := txscript.NewEngine(contractTxOut.PkScript, refundTx, 0, txscript.StandardVerifyFlags, + txscript.NewSigCache(10), sigHashes, contractTxOut.Value, a) + if err != nil { + return nil, 0, fmt.Errorf("failed to create verification engine for refund: %v", err) + } + err = engine.Execute() + if err != nil { + return nil, 0, fmt.Errorf("failed to execute verification engine for refund: %v", err) + } + } + + return refundTx, fee, nil +} + +func sha256Hash(x []byte) []byte { + h := sha256.Sum256(x) + return h[:] +} + +func calcFeePerKb(absoluteFee btcutil.Amount, serializeSize int) float64 { + return float64(absoluteFee) / float64(serializeSize) / 1e5 +} + +func getNewAddress(c *rpc.Client) (string, error) { + rawResp, err := c.RawRequest("getnewaddress", nil) + if err != nil { + return "", err + } + var addrStr string + err = json.Unmarshal(rawResp, &addrStr) + if err != nil { + return "", err + } + addr, err := btcutil.DecodeAddress(addrStr, chainParams) + if err != nil { + return "", err + } + if !addr.IsForNet(chainParams) { + return "", fmt.Errorf("address %v is not intended for use on %v", + addrStr, chainParams.Name) + } + return addr.String(), nil +} + +// getSchnorrPubKey returns a pub key that can be used for schnorr signing. +// Only pub keys with even y coordinates are supported. +func getSchnorrPubKey(c *rpc.Client) (*secp256k1.PublicKey, error) { + var pubKeyB []byte + for { + rawAddrResp, err := c.RawRequest("getnewaddress", nil) + if err != nil { + return nil, err + } + type addressInfoResp struct { + PubKey string `json:"pubkey"` + } + rawInfoResp, err := c.RawRequest("getaddressinfo", []json.RawMessage{rawAddrResp}) + if err != nil { + return nil, err + } + infoResp := addressInfoResp{} + err = json.Unmarshal(rawInfoResp, &infoResp) + if err != nil { + return nil, err + } + pubKeyB, err = hex.DecodeString(infoResp.PubKey) + if err != nil { + return nil, err + } + if len(pubKeyB) != 33 { + return nil, fmt.Errorf("pubkey %x is not 33 bytes", pubKeyB) + } + if pubKeyB[0] == secp256k1.PubKeyFormatCompressedEven { + break + } + } + + pk, err := btcec.ParsePubKey(pubKeyB) + if err != nil { + return nil, err + } + + return pk, nil +} + +func (cmd *initiateCmd) runCommand(c *rpc.Client) error { + var secret [secretSize]byte + _, err := rand.Read(secret[:]) + if err != nil { + return err + } + secretHash := sha256Hash(secret[:]) + + // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted + // as a unix time rather than a block height. + locktime := time.Now().Add(48 * time.Hour).Unix() + + b, err := buildContract(c, &contractArgs{ + them: cmd.cp2Addr, + amount: cmd.amount, + locktime: locktime, + secretHash: secretHash, + }) + if err != nil { + return err + } + + refundTxHash := b.refundTx.TxHash() + contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize()) + refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize()) + + fmt.Printf("Secret: %x\n", secret) + fmt.Printf("Secret hash: %x\n\n", secretHash) + fmt.Printf("Contract fee: %v (%0.8f BTC/kB)\n", b.contractFee, contractFeePerKb) + fmt.Printf("Refund fee: %v (%0.8f BTC/kB)\n\n", b.refundFee, refundFeePerKb) + fmt.Printf("Contract (%v):\n", b.contractP2SH) + fmt.Printf("%x\n\n", b.contract) + var contractBuf bytes.Buffer + contractBuf.Grow(b.contractTx.SerializeSize()) + b.contractTx.Serialize(&contractBuf) + fmt.Printf("Contract transaction (%v):\n", b.contractTxHash) + fmt.Printf("%x\n\n", contractBuf.Bytes()) + var refundBuf bytes.Buffer + refundBuf.Grow(b.refundTx.SerializeSize()) + b.refundTx.Serialize(&refundBuf) + fmt.Printf("Refund transaction (%v):\n", &refundTxHash) + fmt.Printf("%x\n\n", refundBuf.Bytes()) + + return promptPublishTx(c, b.contractTx, "contract") +} + +func (cmd *participateCmd) runCommand(c *rpc.Client) error { + // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted + // as a unix time rather than a block height. + locktime := time.Now().Add(24 * time.Hour).Unix() + + b, err := buildContract(c, &contractArgs{ them: cmd.cp1Addr, amount: cmd.amount, locktime: locktime, @@ -981,9 +1713,11 @@ func (cmd *redeemCmd) runCommand(c *rpc.Client) error { fmt.Printf("%x\n\n", buf.Bytes()) if verify { + prevOut := cmd.contractTx.TxOut[contractOut] + a := txscript.NewCannedPrevOutputFetcher(prevOut.PkScript, prevOut.Value) e, err := txscript.NewEngine(cmd.contractTx.TxOut[contractOutPoint.Index].PkScript, redeemTx, 0, txscript.StandardVerifyFlags, txscript.NewSigCache(10), - txscript.NewTxSigHashes(redeemTx), cmd.contractTx.TxOut[contractOut].Value) + txscript.NewTxSigHashes(redeemTx, a), cmd.contractTx.TxOut[contractOut].Value, a) if err != nil { panic(err) } @@ -1123,12 +1857,372 @@ func (cmd *auditContractCmd) runOfflineCommand() error { return nil } +func (cmd *lockFundsCmd) runCommand(c *rpc.Client) error { + pubKey, err := getSchnorrPubKey(c) + if err != nil { + return err + } + + var lockTime int64 + if cmd.initiator { + lockTime = time.Now().Add(48 * time.Hour).Unix() + } else { + lockTime = time.Now().Add(24 * time.Hour).Unix() + } + + var buf [32]byte + if _, err := rand.Read(buf[:]); err != nil { + return err + } + var internalKeyNonce secp256k1.ModNScalar + internalKeyNonce.SetBytes(&buf) + internalKey := fakeInternalKey(&internalKeyNonce) + + b, err := buildPrivateContract(c, &privateContractArgs{ + us: pubKey, + them: cmd.cpPubKey, + amount: cmd.amount, + locktime: lockTime, + internalKey: internalKey, + }) + if err != nil { + return err + } + + var lockTxBuff bytes.Buffer + b.contractTx.Serialize(&lockTxBuff) + + var refundBuff bytes.Buffer + b.refundTx.Serialize(&refundBuff) + + contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize()) + refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize()) + + fmt.Printf("\nContract fee: %v (%0.8f BTC/kB)\n", b.contractFee, contractFeePerKb) + fmt.Printf("Refund fee: %v (%0.8f BTC/kB)\n\n", b.refundFee, refundFeePerKb) + fmt.Printf("Redeem swap contract:\n%x\n\n", b.redeemContract) + fmt.Printf("Refund swap contract:\n%x\n\n", b.refundContract) + fmt.Printf("Internal key nonce:\n%x\n\n", internalKeyNonce.Bytes()) + fmt.Printf("Lock tx (%v):\n%x\n\n", b.contractTx.TxHash(), lockTxBuff.Bytes()) + fmt.Printf("Redeem tx (%v):\n%x\n\n", b.refundTx.TxHash(), refundBuff.Bytes()) + + return promptPublishTx(c, b.contractTx, "contract") +} + +func (cmd *unsignedRedemptionCmd) runCommand(c *rpc.Client) error { + address, err := getNewAddress(c) + if err != nil { + return fmt.Errorf("error getting new address: %v", err) + } + addr, err := btcutil.DecodeAddress(address, chainParams) + if err != nil { + return err + } + outScript, err := txscript.PayToAddrScript(addr) + if err != nil { + return err + } + + internalKey := fakeInternalKey(cmd.cpTxInternalKeyNonce) + + contractOutpoint, err := getPrivateContractOutpoint(cmd.cpLockTx, cmd.cpRedeemContract, cmd.cpRefundContract, internalKey) + if err != nil { + return err + } + contractTxOut := cmd.cpLockTx.TxOut[contractOutpoint.Index] + + redeemTx := wire.NewMsgTx(txVersion) + redeemTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{ + Hash: contractOutpoint.Hash, + Index: contractOutpoint.Index, + }, nil, nil)) + redeemTx.AddTxOut(wire.NewTxOut(0, outScript)) + refundSize := estimatePrivateRedeemSerializeSize(redeemTx.TxOut) + feePerKb, minFeePerKb, err := getFeePerKb(c) + if err != nil { + return err + } + + fee := txrules.FeeForSerializeSize(feePerKb, refundSize) + redeemTx.TxOut[0].Value = contractTxOut.Value - int64(fee) + if txrules.IsDustOutput(redeemTx.TxOut[0], minFeePerKb) { + return fmt.Errorf("redeem output value of %v is dust", btcutil.Amount(redeemTx.TxOut[0].Value)) + } + + var buf bytes.Buffer + redeemTx.Serialize(&buf) + + fmt.Printf("\nRedeem fee: %v (%0.8f BTC/kB)\n\n", fee, calcFeePerKb(fee, redeemTx.SerializeSize())) + fmt.Printf("Unsigned redemption:\n%x\n", buf.Bytes()) + return nil +} + +func (cmd *initiateAdaptorCmd) runCommand(c *rpc.Client) error { + ourPK, _, err := extractPrivateRedeemDetails(cmd.ourRedeemContract) + if err != nil { + return err + } + ourSchnorrPK, err := schnorr.ParsePubKey(ourPK) + if err != nil { + return err + } + + privKey, err := getPrivKeyFromPubKey(c, ourSchnorrPK) + if err != nil { + return err + } + + internalKey := fakeInternalKey(cmd.ourTxInternalKeyNonce) + + contractOutPoint, err := getPrivateContractOutpoint(cmd.ourLockTx, cmd.ourRedeemContract, cmd.ourRefundContract, internalKey) + if err != nil { + return err + } + contractTxOut := cmd.ourLockTx.TxOut[contractOutPoint.Index] + + a := txscript.NewCannedPrevOutputFetcher(contractTxOut.PkScript, contractTxOut.Value) + sigHashes := txscript.NewTxSigHashes(cmd.cpUnsignedRedeemTx, a) + + sigB, err := txscript.RawTxInTapscriptSignature( + cmd.cpUnsignedRedeemTx, sigHashes, 0, contractTxOut.Value, contractTxOut.PkScript, + txscript.NewBaseTapLeaf(cmd.ourRedeemContract), txscript.SigHashDefault, privKey) + if err != nil { + return err + } + + var tBuf [32]byte + if _, err := rand.Read(tBuf[:]); err != nil { + return err + } + var tweak secp256k1.ModNScalar + tweak.SetBytes(&tBuf) + + sig, err := schnorr.ParseSignature(sigB[:64]) + if err != nil { + return err + } + + adaptorSig := adaptor.PrivateKeyTweakedAdaptorSig(sig, privKey.PubKey(), &tweak) + + fmt.Printf("\nAdaptor signature:\n%x\n", adaptorSig.Serialize()) + fmt.Printf("\nTweak:\n%x\n", tBuf[:]) + return nil +} + +func (cmd *verifyAdaptorCmd) runCommand(c *rpc.Client) error { + internalKey := fakeInternalKey(cmd.cpTxInternalKeyNonce) + + theirPKB, _, err := extractPrivateRedeemDetails(cmd.cpRedeemContract) + if err != nil { + return err + } + theirPK, err := schnorr.ParsePubKey(theirPKB) + if err != nil { + return err + } + + contractOutPoint, err := getPrivateContractOutpoint(cmd.cpLockTx, cmd.cpRedeemContract, cmd.cpRefundContract, internalKey) + if err != nil { + return err + } + contractTxOut := cmd.cpLockTx.TxOut[contractOutPoint.Index] + + a := txscript.NewCannedPrevOutputFetcher(contractTxOut.PkScript, contractTxOut.Value) + sigHashes := txscript.NewTxSigHashes(cmd.ourUnsignedRedeem, a) + ourRedemptionSigHash, err := txscript.CalcTapscriptSignaturehash(sigHashes, txscript.SigHashDefault, + cmd.ourUnsignedRedeem, 0, a, txscript.NewBaseTapLeaf(cmd.cpRedeemContract)) + if err != nil { + return err + } + + if err := cmd.cpAdaptorSig.Verify(ourRedemptionSigHash, theirPK); err != nil { + return err + } + + fmt.Println("\nAdaptor sig is valid!") + return nil +} + +func (cmd *participateAdaptorCmd) runCommand(c *rpc.Client) error { + ourPK, _, err := extractPrivateRedeemDetails(cmd.ourRedeemContract) + if err != nil { + return err + } + ourSchnorrPK, err := schnorr.ParsePubKey(ourPK) + if err != nil { + return err + } + + privKey, err := getPrivKeyFromPubKey(c, ourSchnorrPK) + if err != nil { + return err + } + + internaleKey := fakeInternalKey(cmd.ourTxInternalKeyNonce) + + contractOutPoint, err := getPrivateContractOutpoint(cmd.ourLockTx, cmd.ourRedeemContract, cmd.ourRefundContract, internaleKey) + if err != nil { + return err + } + contractTxOut := cmd.ourLockTx.TxOut[contractOutPoint.Index] + + a := txscript.NewCannedPrevOutputFetcher(contractTxOut.PkScript, contractTxOut.Value) + sigHashes := txscript.NewTxSigHashes(cmd.cpUnsignedRedeemTx, a) + sigHash, err := txscript.CalcTapscriptSignaturehash(sigHashes, txscript.SigHashDefault, + cmd.cpUnsignedRedeemTx, 0, a, txscript.NewBaseTapLeaf(cmd.ourRedeemContract)) + if err != nil { + return err + } + + adaptorSig, err := adaptor.PublicKeyTweakedAdaptorSig(privKey, sigHash, cmd.cpAdaptor.PublicTweak()) + if err != nil { + return err + } + + fmt.Printf("\nAdaptor signature: %x\n", adaptorSig.Serialize()) + return nil +} + +func (cmd *privateRedeemCmd) runCommand(c *rpc.Client) error { + _, ourPK, err := extractPrivateRedeemDetails(cmd.cpRedeemContract) + if err != nil { + return err + } + ourSchnorrPK, err := schnorr.ParsePubKey(ourPK) + if err != nil { + return err + } + privKey, err := getPrivKeyFromPubKey(c, ourSchnorrPK) + if err != nil { + return err + } + defer privKey.Zero() + + internalKey := fakeInternalKey(cmd.cpTxInternalKeyNonce) + + contractOutPoint, err := getPrivateContractOutpoint(cmd.cpLockTx, cmd.cpRedeemContract, cmd.cpRefundContract, internalKey) + if err != nil { + return err + } + contractTxOut := cmd.cpLockTx.TxOut[contractOutPoint.Index] + + redeemTx := cmd.unsignedRedemption + + a := txscript.NewMultiPrevOutFetcher(map[wire.OutPoint]*wire.TxOut{ + *contractOutPoint: { + Value: contractTxOut.Value, + PkScript: contractTxOut.PkScript, + }, + }) + sigHashes := txscript.NewTxSigHashes(redeemTx, a) + sigMe, err := txscript.RawTxInTapscriptSignature( + redeemTx, sigHashes, 0, contractTxOut.Value, contractTxOut.PkScript, + txscript.NewBaseTapLeaf(cmd.cpRedeemContract), txscript.SigHashDefault, + privKey) + if err != nil { + return err + } + + sigHash, err := txscript.CalcTapscriptSignaturehash(sigHashes, txscript.SigHashDefault, redeemTx, + 0, a, txscript.NewBaseTapLeaf(cmd.cpRedeemContract)) + if err != nil { + return err + } + sigThem, err := cmd.cpAdaptor.Decrypt(cmd.tweak, sigHash) + if err != nil { + return err + } + controlBlockB, err := getRedeemControlBlock(cmd.cpRedeemContract, cmd.cpRefundContract, internalKey) + if err != nil { + return err + } + redeemTx.TxIn[0].Witness = wire.TxWitness{sigMe, sigThem.Serialize(), cmd.cpRedeemContract, controlBlockB} + + if verify { + engine, err := txscript.NewEngine(contractTxOut.PkScript, redeemTx, 0, txscript.StandardVerifyFlags, + txscript.NewSigCache(10), sigHashes, contractTxOut.Value, a) + if err != nil { + return err + } + err = engine.Execute() + if err != nil { + return err + } + } + + return promptPublishTx(c, redeemTx, "redeem") +} + +func (cmd *extractTweakCmd) runCommand(c *rpc.Client) error { + decryptedSig := cmd.cpRedeemTx.TxIn[0].Witness[1] + sig, err := schnorr.ParseSignature(decryptedSig) + if err != nil { + return err + } + + tweak, err := cmd.ourAdaptor.RecoverTweak(sig) + if err != nil { + return err + } + + fmt.Printf("\nTweak: %x\n", tweak.Bytes()) + return nil +} + +func (cmd *auditPrivateContractCmd) runCommand(c *rpc.Client) error { + creatorPK, participantPK, err := extractPrivateRedeemDetails(cmd.redeemContract) + if err != nil { + return err + } + + _, lockTime, err := extractPrivateRefundDetails(cmd.refundContract) + if err != nil { + return err + } + + internalKey := fakeInternalKey(cmd.internalKeyNonce) + outpoint, err := getPrivateContractOutpoint(cmd.lockTx, cmd.redeemContract, cmd.refundContract, internalKey) + if err != nil { + return err + } + output := cmd.lockTx.TxOut[outpoint.Index] + + fmt.Printf("\nContract value: %v\n", btcutil.Amount(output.Value)) + fmt.Printf("Creator PK: %x\n", append([]byte{02}, creatorPK...)) + fmt.Printf("Participant PK: %x\n", append([]byte{02}, participantPK...)) + + if lockTime >= int64(txscript.LockTimeThreshold) { + t := time.Unix(lockTime, 0) + fmt.Printf("Locktime: %v\n", t.UTC()) + reachedAt := time.Until(t).Truncate(time.Second) + if reachedAt > 0 { + fmt.Printf("Locktime reached in %v\n", reachedAt) + } else { + fmt.Printf("Contract refund time lock has expired\n") + } + } else { + fmt.Printf("Locktime: block %v\n", lockTime) + } + + return nil +} + +func (cmd *getPubKeyCmd) runCommand(c *rpc.Client) error { + pubKey, err := getSchnorrPubKey(c) + if err != nil { + return err + } + + fmt.Printf("%x\n", pubKey.SerializeCompressed()) + return nil +} + // atomicSwapContract returns an output script that may be redeemed by one of // two signature scripts: // -// 1 +// 1 // -// 0 +// 0 // // The first signature script is the normal redemption path done by the other // party and requires the initiator's secret. The second signature script is @@ -1182,6 +2276,95 @@ func atomicSwapContract(pkhMe, pkhThem *[ripemd160.Size]byte, locktime int64, se return b.Script() } +// privateAtomicSwapRedeemScript returns the regular (non-refund) script +// to redeem a private atomic swap. This is one of the leaves in the tapscript +// tree. +func privateAtomicSwapRedeemScript(pkMe, pkThem *secp256k1.PublicKey) ([]byte, error) { + b := txscript.NewScriptBuilder() + b.AddData(schnorr.SerializePubKey(pkMe)) + b.AddOp(txscript.OP_CHECKSIGVERIFY) + b.AddData(schnorr.SerializePubKey(pkThem)) + b.AddOp(txscript.OP_CHECKSIG) + return b.Script() +} + +// privateAtomicSwapRefundScript returns the refund script to spend a private +// atomic swap output after the locktime has been reached. This is one of the +// leaves in the tapscript tree. +func privateAtomicSwapRefundScript(pkMe *secp256k1.PublicKey, lockTime int64) ([]byte, error) { + b := txscript.NewScriptBuilder() + b.AddInt64(lockTime) + b.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + b.AddOp(txscript.OP_DROP) + b.AddData(schnorr.SerializePubKey(pkMe)) + b.AddOp(txscript.OP_CHECKSIG) + script, err := b.Script() + return script, err +} + +func extractPrivateRedeemDetails(script []byte) (creator, participant []byte, err error) { + if len(script) != 68 { + err = fmt.Errorf("invalid swap contract length %d", len(script)) + return + } + + if script[0] == txscript.OP_DATA_32 && + // creator's PK (32 bytes) + script[33] == txscript.OP_CHECKSIGVERIFY && + script[34] == txscript.OP_DATA_32 && + // participant's PK (32 bytes) + script[67] == txscript.OP_CHECKSIG { + creator = make([]byte, 32) + participant = make([]byte, 32) + copy(creator, script[1:33]) + copy(participant, script[35:67]) + } else { + err = fmt.Errorf("invalid swap contract") + } + + return +} + +func extractPrivateRefundDetails(script []byte) (creatorPK []byte, lockTime int64, err error) { + tokenizer := txscript.MakeScriptTokenizer(0, script) + + // locktime (4/8 bytes) + if !tokenizer.Next() { + return nil, 0, fmt.Errorf("invalid refund contract") + } + if tokenizer.Opcode() == txscript.OP_DATA_4 { + lockTime = int64(binary.LittleEndian.Uint32(tokenizer.Data())) + } else if tokenizer.Opcode() == txscript.OP_DATA_8 { + lockTime = int64(binary.LittleEndian.Uint64(tokenizer.Data())) + } else { + return nil, 0, fmt.Errorf("invalid refund contract") + } + + // OP_CHECKLOCKTIMEVERIFY + if !tokenizer.Next() || tokenizer.Opcode() != txscript.OP_CHECKLOCKTIMEVERIFY { + return nil, 0, fmt.Errorf("invalid refund contract") + } + + // OP_DROP + if !tokenizer.Next() || tokenizer.Opcode() != txscript.OP_DROP { + return nil, 0, fmt.Errorf("invalid refund contract") + } + + // creator's PK (32 bytes) + if !tokenizer.Next() || tokenizer.Opcode() != txscript.OP_DATA_32 { + return nil, 0, fmt.Errorf("invalid refund contract") + } + creatorPK = make([]byte, 32) + copy(creatorPK, tokenizer.Data()) + + // OP_CHECKSIG + if !tokenizer.Next() || tokenizer.Opcode() != txscript.OP_CHECKSIG { + return nil, 0, fmt.Errorf("invalid refund contract") + } + + return +} + // redeemP2SHContract returns the signature script to redeem a contract output // using the redeemer's signature and the initiator's secret. This function // assumes P2SH and appends the contract as the final data push. @@ -1206,3 +2389,46 @@ func refundP2SHContract(contract, sig, pubkey []byte) ([]byte, error) { b.AddData(contract) return b.Script() } + +// payToTaprootScript returns the PKScript for a pay-to-taproot output. +func payToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) { + return txscript.NewScriptBuilder(). + AddOp(txscript.OP_1). + AddData(schnorr.SerializePubKey(taprootKey)). + Script() +} + +// getRedeemControlBlock returns the control that should be used when redeeming +// a private atomic swap output. +func getRedeemControlBlock(redeemScript, refundScript []byte, internalKey *secp256k1.PublicKey) ([]byte, error) { + normalLeaf := txscript.NewBaseTapLeaf(redeemScript) + refundLeaf := txscript.NewBaseTapLeaf(refundScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(normalLeaf, refundLeaf) + controlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(internalKey) + return controlBlock.ToBytes() +} + +// getPrivateContractOutpoint returns the outpoint of a private atomic swap +// contract output. If the contract output is not found in the transaction, an +// error is returned. +func getPrivateContractOutpoint(tx *wire.MsgTx, redeemScript, refundScript []byte, internalKey *secp256k1.PublicKey) (*wire.OutPoint, error) { + normalLeaf := txscript.NewBaseTapLeaf(redeemScript) + refundLeaf := txscript.NewBaseTapLeaf(refundScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(normalLeaf, refundLeaf) + tapScriptRootHash := tapScriptTree.RootNode.TapHash() + outputKey := txscript.ComputeTaprootOutputKey(internalKey, tapScriptRootHash[:]) + expectedPkScript, err := payToTaprootScript(outputKey) + if err != nil { + return nil, err + } + contractOutpoint := &wire.OutPoint{Hash: tx.TxHash(), Index: ^uint32(0)} + for i, out := range tx.TxOut { + if bytes.Equal(out.PkScript, expectedPkScript) { + contractOutpoint.Index = uint32(i) + } + } + if contractOutpoint.Index == ^uint32(0) { + return nil, fmt.Errorf("contract outpoint not found") + } + return contractOutpoint, nil +} diff --git a/cmd/btcatomicswap/sizeest.go b/cmd/btcatomicswap/sizeest.go index 799ffde..3c0bc9c 100644 --- a/cmd/btcatomicswap/sizeest.go +++ b/cmd/btcatomicswap/sizeest.go @@ -35,6 +35,25 @@ const ( // - 33 bytes serialized compressed pubkey // - OP_FALSE refundAtomicSwapSigScriptSize = 1 + 73 + 1 + 33 + 1 + + // redeemPrivateAtomicSwapWitnessSize is the worst case (largest) serialize + // size of a transaction witness for redeeming a private atomic swap. + // + // - Num elements (1 byte) + // - Signature 1 (1 + 64 bytes) + // - Signature 2 (1 + 64 bytes) + // - Redeem script (1 + 68 bytes) + // - Control block (1 + 65 bytes) + redeemPrivateAtomicSwapWitnessSize = 1 + 1 + 64 + 1 + 64 + 1 + 68 + 1 + 65 // 266 + + // refundPrivateAtomicSwapWitnessSize is the worst case (largest) serialize + // size of a transaction witness for refunding a private atomic swap. + // + // - Num elements (1 byte) + // - Signature (1 + 64 bytes) + // - Refund script (1 + 41 bytes) + // - Control block (1 + 65 bytes) + refundPrivateAtomicSwapWitnessSize = 1 + 1 + 64 + 1 + 41 + 1 + 65 // 174 ) func sumOutputSerializeSizes(outputs []*wire.TxOut) (serializeSize int) { @@ -89,3 +108,23 @@ func estimateRefundSerializeSize(contract []byte, txOuts []*wire.TxOut) int { inputSize(refundAtomicSwapSigScriptSize+contractPushSize) + sumOutputSerializeSizes(txOuts) } + +// estimatePrivateRedeemSerializeSize returns a worst case serialize size +// estimates for a transaction that redeems a private atomic swap P2TR output. +func estimatePrivateRedeemSerializeSize(txOuts []*wire.TxOut) int { + // 12 additional bytes are for version, locktime and expiry. + return 12 + wire.VarIntSerializeSize(1) + + wire.VarIntSerializeSize(uint64(len(txOuts))) + + inputSize((redeemPrivateAtomicSwapWitnessSize+3)/4) + + sumOutputSerializeSizes(txOuts) +} + +// estimatePrivateRefundSerializeSize returns a worst case serialize size +// estimates for a transaction that refunds a private atomic swap P2TR output. +func estimatePrivateRefundSerializeSize(txOuts []*wire.TxOut) int { + // 12 additional bytes are for version, locktime and expiry. + return 12 + wire.VarIntSerializeSize(1) + + wire.VarIntSerializeSize(uint64(len(txOuts))) + + inputSize((refundPrivateAtomicSwapWitnessSize+3)/4) + + sumOutputSerializeSizes(txOuts) +} diff --git a/cmd/dcratomicswap/adaptor/adaptor.go b/cmd/dcratomicswap/adaptor/adaptor.go new file mode 100644 index 0000000..797e8f2 --- /dev/null +++ b/cmd/dcratomicswap/adaptor/adaptor.go @@ -0,0 +1,496 @@ +package adaptor + +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 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.ToAffine() + 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.SetByteSlice(b[0:32]); overflow { + str := "invalid signature: r >= field prime" + return nil, errors.New(str) + } + + var s secp256k1.ModNScalar + if overflow := s.SetByteSlice(b[32:64]); overflow { + str := "invalid signature: s >= group order" + return nil, errors.New(str) + } + + var t secp256k1.JacobianPoint + if overflow := t.X.SetByteSlice(b[64:96]); overflow { + str := "invalid signature: t.x >= field prime" + return nil, errors.New(str) + } + + if overflow := t.Y.SetByteSlice(b[96:128]); overflow { + str := "invalid signature: t.y >= field prime" + return nil, errors.New(str) + } + + t.Z.SetInt(1) + + var pubKeyTweak bool + if b[128] == byte(1) { + pubKeyTweak = true + } + + 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, hash []byte) (*schnorr.Signature, error) { + var expectedT secp256k1.JacobianPoint + secp256k1.ScalarBaseMultNonConst(tweak, &expectedT) + expectedT.ToAffine() + sig.t.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).Add(&sig.s).Negate().Add(s) + + // Verify the recovered tweak + var expectedT secp256k1.JacobianPoint + secp256k1.ScalarBaseMultNonConst(t, &expectedT) + expectedT.ToAffine() + sig.t.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) + return &AdaptorSignature{ + r: *r, + s: *s, + t: *T, + 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 &AdaptorSignature{ + r: sig.r, + s: sig.s, + t: *T, + pubKeyTweak: true, + }, nil + } +} + +func parseSig(sig *schnorr.Signature) (s *secp256k1.ModNScalar, r *secp256k1.FieldVal) { + sigB := sig.Serialize() + r = new(secp256k1.FieldVal) + r.SetByteSlice(sigB[0:32]) + s = new(secp256k1.ModNScalar) + s.SetByteSlice(sigB[32:64]) + return +} + +// 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) + + s, r := parseSig(sig) + tweakedS := new(secp256k1.ModNScalar).Add(s).Add(t) + + return &AdaptorSignature{ + r: *r, + s: *tweakedS, + t: *T, + } +} diff --git a/cmd/dcratomicswap/adaptor/adaptor_test.go b/cmd/dcratomicswap/adaptor/adaptor_test.go new file mode 100644 index 0000000..da5883c --- /dev/null +++ b/cmd/dcratomicswap/adaptor/adaptor_test.go @@ -0,0 +1,131 @@ +package adaptor + +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, hash2[:]) + 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, hash1[:]) + 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/cmd/dcratomicswap/go.mod b/cmd/dcratomicswap/go.mod index e9f45f7..a5b2aaf 100644 --- a/cmd/dcratomicswap/go.mod +++ b/cmd/dcratomicswap/go.mod @@ -3,13 +3,23 @@ module github.com/decred/atomicswap/cmd/dcratomicswap go 1.15 require ( - decred.org/dcrwallet v1.6.0-rc3 - github.com/decred/dcrd/chaincfg/chainhash v1.0.2 - github.com/decred/dcrd/chaincfg/v3 v3.0.0 - github.com/decred/dcrd/dcrec v1.0.0 - github.com/decred/dcrd/dcrutil/v3 v3.0.0 - github.com/decred/dcrd/txscript/v3 v3.0.0 - github.com/decred/dcrd/wire v1.4.0 - golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a - google.golang.org/grpc v1.32.0 + decred.org/dcrwallet/v3 v3.1.0 + github.com/decred/dcrd/chaincfg/chainhash v1.0.4 + github.com/decred/dcrd/chaincfg/v3 v3.2.0 + github.com/decred/dcrd/crypto/blake256 v1.0.1 + github.com/decred/dcrd/dcrec v1.0.1 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 + github.com/decred/dcrd/dcrutil/v4 v4.0.1 + github.com/decred/dcrd/txscript/v4 v4.1.0 + github.com/decred/dcrd/wire v1.6.0 + golang.org/x/crypto v0.14.0 + google.golang.org/grpc v1.46.0 +) + +require ( + decred.org/dcrwallet/v4 v4.0.0-20231122100737-51c6c5773350 // indirect + github.com/google/go-cmp v0.5.9 // indirect + golang.org/x/net v0.16.0 // indirect + google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect + google.golang.org/protobuf v1.28.1 // indirect ) diff --git a/cmd/dcratomicswap/go.sum b/cmd/dcratomicswap/go.sum index b0715f7..0b5dd2b 100644 --- a/cmd/dcratomicswap/go.sum +++ b/cmd/dcratomicswap/go.sum @@ -1,68 +1,84 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -decred.org/cspp v0.3.0/go.mod h1:UygjYilC94dER3BEU65Zzyoqy9ngJfWCD2rdJqvUs2A= -decred.org/dcrwallet v1.6.0-rc3 h1:Dft0ArM2hpqbjyYdhPEBbsTPah19Kno1Aa5WI5n4zn0= -decred.org/dcrwallet v1.6.0-rc3/go.mod h1:KfDFVvtUnyCKPp6WwWxgD5Q5nlcgvd826tVOHyPOBU0= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +decred.org/cspp/v2 v2.1.0/go.mod h1:9nO3bfvCheOPIFZw5f6sRQ42CjBFB5RKSaJ9Iq6G4MA= +decred.org/dcrwallet/v3 v3.1.0 h1:JCPnF6ENtkeyWLLhyR6d6hzPAFccbbD0u2Fv2E2mA00= +decred.org/dcrwallet/v3 v3.1.0/go.mod h1:KYWzL2R6ghBLSvB7XXU9S29QwgcqnApCvONMDJ6KCR0= +decred.org/dcrwallet/v4 v4.0.0-20231122100737-51c6c5773350 h1:Dc1Qa7WKVFzzA6pGiggsrG3pNucHRALMmKuLK2fjwNQ= +decred.org/dcrwallet/v4 v4.0.0-20231122100737-51c6c5773350/go.mod h1:TcBr1Tqk7+1tzuuBpv0KpeXPti5uLUCHpfDaozzVh8U= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a/go.mod h1:z/9Ck1EDixEbBbZ2KH2qNHekEmDLTOZ+FyoIPWWSVOI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= -github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= -github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI= -github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= -github.com/decred/dcrd/addrmgr v1.2.0/go.mod h1:QlZF9vkzwYh0qs25C76SAFZBRscjETga/K28GEE6qIc= -github.com/decred/dcrd/blockchain/stake/v3 v3.0.0/go.mod h1:5GIUwsrHQCJauacgCegIR6t92SaeVi28Qls/BLN9vOw= -github.com/decred/dcrd/blockchain/standalone/v2 v2.0.0 h1:9gUuH0u/IZNPWBK9K3CxgAWPG7nTqVSsZefpGY4Okns= -github.com/decred/dcrd/blockchain/standalone/v2 v2.0.0/go.mod h1:t2qaZ3hNnxHZ5kzVJDgW5sp47/8T5hYJt7SR+/JtRhI= -github.com/decred/dcrd/blockchain/v3 v3.0.1/go.mod h1:LD5VA95qdb+DlRiPI8VLBimDqvlDCAJsidZ5oD6nc/U= -github.com/decred/dcrd/certgen v1.1.1/go.mod h1:ivkPLChfjdAgFh7ZQOtl6kJRqVkfrCq67dlq3AbZBQE= -github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU= -github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= -github.com/decred/dcrd/chaincfg/v3 v3.0.0 h1:+TFbu7ZmvBwM+SZz5mrj6cun9ts/6DAL5sqnsaFBHGQ= -github.com/decred/dcrd/chaincfg/v3 v3.0.0/go.mod h1:EspyubQ7D2w6tjP7rBGDIE7OTbuMgBjR2F2kZFnh31A= -github.com/decred/dcrd/connmgr/v3 v3.0.0/go.mod h1:cPI43Aggp1lOhrVG75eJ3c3BwuFx0NhT77FK34ky+ak= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/crypto/ripemd160 v1.0.1 h1:TjRL4LfftzTjXzaufov96iDAkbY2R3aTvH2YMYa1IOc= -github.com/decred/dcrd/crypto/ripemd160 v1.0.1/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg= -github.com/decred/dcrd/database/v2 v2.0.2/go.mod h1:S78KbTCCJWUTJDVTByiQuB+HmL0DM2vIMsa2WsrF9KM= -github.com/decred/dcrd/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o= -github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8= -github.com/decred/dcrd/dcrec/edwards/v2 v2.0.1 h1:V6eqU1crZzuoFT4KG2LhaU5xDSdkHuvLQsj25wd7Wb4= -github.com/decred/dcrd/dcrec/edwards/v2 v2.0.1/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= -github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw= -github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= -github.com/decred/dcrd/dcrjson/v3 v3.1.0/go.mod h1:fnTHev/ABGp8IxFudDhjGi9ghLiXRff1qZz/wvq12Mg= -github.com/decred/dcrd/dcrutil/v3 v3.0.0 h1:n6uQaTQynIhCY89XsoDk2WQqcUcnbD+zUM9rnZcIOZo= -github.com/decred/dcrd/dcrutil/v3 v3.0.0/go.mod h1:iVsjcqVzLmYFGCZLet2H7Nq+7imV9tYcuY+0lC2mNsY= -github.com/decred/dcrd/gcs/v2 v2.1.0/go.mod h1:MbnJOINFcp42NMRAQ+CjX/xGz+53AwNgMzKZhwBibdM= -github.com/decred/dcrd/hdkeychain/v3 v3.0.0/go.mod h1:Vz7PJSlLzhqmOR2lmjGD9JqAZgmUnM8P6r8hg7U4Zho= -github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.2.0/go.mod h1:krn89ZOgSa8yc7sA4WpDK95p61NnjNWFkNlMnGrKbMc= -github.com/decred/dcrd/txscript/v3 v3.0.0 h1:74NmirXAIskbGP0g9OWtrmN7OxDbWJ9G73a5uoxTkcM= -github.com/decred/dcrd/txscript/v3 v3.0.0/go.mod h1:pdvnlD4KGdDoc09cvWRJ8EoRQUaiUz41uDevOWuEfII= -github.com/decred/dcrd/wire v1.3.0 h1:X76I2/a8esUmxXmFpJpAvXEi014IA4twgwcOBeIS8lE= -github.com/decred/dcrd/wire v1.3.0/go.mod h1:fnKGlUY2IBuqnpxx5dYRU5Oiq392OBqAuVjRVSkIoXM= -github.com/decred/dcrd/wire v1.4.0 h1:KmSo6eTQIvhXS0fLBQ/l7hG7QLcSJQKSwSyzSqJYDk0= -github.com/decred/dcrd/wire v1.4.0/go.mod h1:WxC/0K+cCAnBh+SKsRjIX9YPgvrjhmE+6pZlel1G7Ro= +github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/decred/base58 v1.0.5 h1:hwcieUM3pfPnE/6p3J100zoRfGkQxBulZHo7GZfOqic= +github.com/decred/base58 v1.0.5/go.mod h1:s/8lukEHFA6bUQQb/v3rjUySJ2hu+RioCzLukAVkrfw= +github.com/decred/dcrd/addrmgr/v2 v2.0.2/go.mod h1:lMupOhByAzVJN7EFWSGLeGTrlvvx38uCY4D+bPf2AT4= +github.com/decred/dcrd/blockchain/stake/v5 v5.0.0/go.mod h1:5sSjMq9THpnrLkW0SjEqIBIo8qq2nXzc+m7k9oFVVmY= +github.com/decred/dcrd/blockchain/standalone/v2 v2.2.0 h1:v3yfo66axjr3oLihct+5tLEeM9YUzvK3i/6e2Im6RO0= +github.com/decred/dcrd/blockchain/standalone/v2 v2.2.0/go.mod h1:JsOpl2nHhW2D2bWMEtbMuAE+mIU/Pdd1i1pmYR+2RYI= +github.com/decred/dcrd/blockchain/v5 v5.0.0/go.mod h1:Khd1xIrEmstkaclyfvf72ecn9KIv4CjEawqBH+icTbA= +github.com/decred/dcrd/certgen v1.1.2/go.mod h1:Od5y39J+r2ZlvrizyWu2cylcYu0+emTTVm3eix4W8bw= +github.com/decred/dcrd/chaincfg/chainhash v1.0.4 h1:zRCv6tdncLfLTKYqu7hrXvs7hW+8FO/NvwoFvGsrluU= +github.com/decred/dcrd/chaincfg/chainhash v1.0.4/go.mod h1:hA86XxlBWwHivMvxzXTSD0ZCG/LoYsFdWnCekkTMCqY= +github.com/decred/dcrd/chaincfg/v3 v3.2.0 h1:6WxA92AGBkycEuWvxtZMvA76FbzbkDRoK8OGbsR2muk= +github.com/decred/dcrd/chaincfg/v3 v3.2.0/go.mod h1:2rHW1TKyFmwZTVBLoU/Cmf0oxcpBjUEegbSlBfrsriI= +github.com/decred/dcrd/connmgr/v3 v3.1.1/go.mod h1:YlRGPagi/6SJbG9CFq2ZnorX9+deRNb6+m0ovkNDcKY= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/crypto/ripemd160 v1.0.2 h1:TvGTmUBHDU75OHro9ojPLK+Yv7gDl2hnUvRocRCjsys= +github.com/decred/dcrd/crypto/ripemd160 v1.0.2/go.mod h1:uGfjDyePSpa75cSQLzNdVmWlbQMBuiJkvXw/MNKRY4M= +github.com/decred/dcrd/database/v3 v3.0.1/go.mod h1:IErr/Z62pFLoPZTMPGxedbcIuseGk0w3dszP3AFbXyw= +github.com/decred/dcrd/dcrec v1.0.1 h1:gDzlndw0zYxM5BlaV17d7ZJV6vhRe9njPBFeg4Db2UY= +github.com/decred/dcrd/dcrec v1.0.1/go.mod h1:CO+EJd8eHFb8WHa84C7ZBkXsNUIywaTHb+UAuI5uo6o= +github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3 h1:l/lhv2aJCUignzls81+wvga0TFlyoZx8QxRMQgXpZik= +github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3/go.mod h1:AKpV6+wZ2MfPRJnTbQ6NPgWrKzbe9RCIlCF/FKzMtM8= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/dcrjson/v4 v4.0.1/go.mod h1:2qVikafVF9/X3PngQVmqkbUbyAl32uik0k/kydgtqMc= +github.com/decred/dcrd/dcrutil/v4 v4.0.1 h1:E+d2TNbpOj0f1L9RqkZkEm1QolFjajvkzxWC5WOPf1s= +github.com/decred/dcrd/dcrutil/v4 v4.0.1/go.mod h1:7EXyHYj8FEqY+WzMuRkF0nh32ueLqhutZDoW4eQ+KRc= +github.com/decred/dcrd/gcs/v4 v4.0.0/go.mod h1:9z+EBagzpEdAumwS09vf/hiGaR8XhNmsBgaVq6u7/NI= +github.com/decred/dcrd/hdkeychain/v3 v3.1.1/go.mod h1:HaabrLc27lnny5/Ph9+6I3szp0op5MCb7smEwlzfD60= +github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0/go.mod h1:dDHO7ivrPAhZjFD3LoOJN/kdq5gi0sxie6zCsWHAiUo= +github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.1.0/go.mod h1:dDHO7ivrPAhZjFD3LoOJN/kdq5gi0sxie6zCsWHAiUo= +github.com/decred/dcrd/rpcclient/v8 v8.0.0/go.mod h1:gx4+DI5apuOEeLwPBJFlMoj3GFWq1I7/X8XCQmMTi8Q= +github.com/decred/dcrd/txscript/v4 v4.1.0 h1:uEdcibIOl6BuWj3AqmXZ9xIK/qbo6lHY9aNk29FtkrU= +github.com/decred/dcrd/txscript/v4 v4.1.0/go.mod h1:OVguPtPc4YMkgssxzP8B6XEMf/J3MB6S1JKpxgGQqi0= +github.com/decred/dcrd/wire v1.6.0 h1:YOGwPHk4nzGr6OIwUGb8crJYWDiVLpuMxfDBCCF7s/o= +github.com/decred/dcrd/wire v1.6.0/go.mod h1:XQ8Xv/pN/3xaDcb7sH8FBLS9cdgVctT7HpBKKGsIACk= github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0= -github.com/decred/slog v1.1.0 h1:uz5ZFfmaexj1rEDgZvzQ7wjGkoSPjw2LCh8K+K1VrW4= -github.com/decred/slog v1.1.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= +github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +github.com/decred/vspd/client/v3 v3.0.0/go.mod h1:5pfPvIa6V38AmophMrKUCl3KMpEIxcltWtgL2R+wsW8= +github.com/decred/vspd/types/v2 v2.1.0/go.mod h1:2xnNqedkt9GuL+pK8uIzDxqYxFlwLRflYFJH64b76n0= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -70,115 +86,197 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/jrick/wsrpc/v2 v2.3.2/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= -github.com/jrick/wsrpc/v2 v2.3.4/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU= +github.com/jrick/wsrpc/v2 v2.3.5/go.mod h1:7oBeDM/xMF6Yqy4GDAjpppuOf1hm6lWsaG3EaMrm+aA= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 h1:q1kiSVscqoDeqTF27eQ2NnLLDmqF0I373qQNXYMy0fo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= diff --git a/cmd/dcratomicswap/main.go b/cmd/dcratomicswap/main.go index 576b508..1ebdd1e 100644 --- a/cmd/dcratomicswap/main.go +++ b/cmd/dcratomicswap/main.go @@ -12,11 +12,11 @@ import ( "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/binary" "encoding/hex" "errors" "flag" "fmt" - "io/ioutil" "net" "os" "path/filepath" @@ -24,16 +24,24 @@ import ( "strings" "time" - pb "decred.org/dcrwallet/rpc/walletrpc" - "decred.org/dcrwallet/wallet/txrules" + pb "decred.org/dcrwallet/v3/rpc/walletrpc" + "decred.org/dcrwallet/v3/wallet/txrules" + "github.com/decred/atomicswap/cmd/dcratomicswap/adaptor" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrec" - "github.com/decred/dcrd/dcrutil/v3" - "github.com/decred/dcrd/txscript/v3" - "github.com/decred/dcrd/wire" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" + "github.com/decred/dcrd/dcrutil/v4" "golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ssh/terminal" + + "github.com/decred/dcrd/txscript/v4" + "github.com/decred/dcrd/txscript/v4/sign" + "github.com/decred/dcrd/txscript/v4/stdaddr" + "github.com/decred/dcrd/txscript/v4/stdscript" + + "github.com/decred/dcrd/wire" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -66,6 +74,7 @@ var ( clientCertFlag = flagset.String("clientcert", "", "path to client authentication certificate") clientKeyFlag = flagset.String("clientkey", "", "path to client authentication key") testnetFlag = flagset.Bool("testnet", false, "use testnet network") + simnetFlag = flagset.Bool("simnet", false, "use simnet network") ) // There are two directions that the atomic swap can be performed, as the @@ -102,6 +111,14 @@ func init() { fmt.Println(" extractsecret ") fmt.Println(" auditcontract ") fmt.Println() + fmt.Println("Private swap commands:") + fmt.Println(" lockfunds ") + fmt.Println(" unsignedredemption ") + fmt.Println(" initiateadaptor ") + fmt.Println(" verifyadaptor ") + fmt.Println(" participateadaptor ") + fmt.Println(" privateredeem ") + fmt.Println(" extracttweak ") fmt.Println("Flags:") flagset.PrintDefaults() } @@ -118,12 +135,12 @@ type offlineCommand interface { } type initiateCmd struct { - cp2Addr dcrutil.Address + cp2Addr *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0 amount dcrutil.Amount } type participateCmd struct { - cp1Addr dcrutil.Address + cp1Addr *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0 amount dcrutil.Amount secretHash []byte } @@ -149,6 +166,58 @@ type auditContractCmd struct { contractTx *wire.MsgTx } +// The following commands are used for private swaps. + +type lockFundsCmd struct { + cpAddr *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0 + amount dcrutil.Amount + initiator bool +} + +type unsignedRedemptionCmd struct { + cpContract []byte + cpLockTx *wire.MsgTx +} + +type initiateAdaptorCmd struct { + ourContract []byte + ourLockTx *wire.MsgTx + cpUnsignedRedeemTx *wire.MsgTx +} + +type verifyAdaptorCmd struct { + cpContract []byte + cpAdaptorSig *adaptor.AdaptorSignature + cpPubKey *secp256k1.PublicKey + ourUnsignedRedeem *wire.MsgTx +} + +type participateAdaptorCmd struct { + ourContract []byte + ourLockTx *wire.MsgTx + cpAdaptor *adaptor.AdaptorSignature + cpUnsignedRedemption *wire.MsgTx +} + +type privateRedeemCmd struct { + cpContract []byte + cpLockTx *wire.MsgTx + cpAdaptor *adaptor.AdaptorSignature + cpPubKey *secp256k1.PublicKey + unsignedRedemption *wire.MsgTx + tweak *secp256k1.ModNScalar +} + +type extractTweakCmd struct { + cpRedeemTx *wire.MsgTx + ourAdaptor *adaptor.AdaptorSignature +} + +type auditPrivateContractCmd struct { + cpContract []byte + cpLockTx *wire.MsgTx +} + func main() { err, showUsage := run() if err != nil { @@ -174,6 +243,27 @@ func checkCmdArgLength(args []string, required int) (nArgs int) { return required } +func parseTx(s string) (*wire.MsgTx, error) { + txB, err := hex.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("failed to decode transaction: %v", err) + } + var tx wire.MsgTx + err = tx.Deserialize(bytes.NewReader(txB)) + if err != nil { + return nil, fmt.Errorf("failed to decode transaction: %v", err) + } + return &tx, nil +} + +func parseAdaptorSig(s string) (*adaptor.AdaptorSignature, error) { + sigB, err := hex.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("failed to decode transaction: %v", err) + } + return adaptor.ParseAdaptorSignature(sigB) +} + func run() (err error, showUsage bool) { flagset.Parse(os.Args[1:]) args := flagset.Args() @@ -184,6 +274,8 @@ func run() (err error, showUsage bool) { switch args[0] { case "initiate": cmdArgs = 2 + case "redeem_private": + cmdArgs = 2 case "participate": cmdArgs = 3 case "redeem": @@ -194,6 +286,22 @@ func run() (err error, showUsage bool) { cmdArgs = 2 case "auditcontract": cmdArgs = 2 + case "lockfunds": + cmdArgs = 3 + case "initiateadaptor": + cmdArgs = 3 + case "participateadaptor": + cmdArgs = 4 + case "unsignedredemption": + cmdArgs = 2 + case "verifyadaptor": + cmdArgs = 4 + case "privateredeem": + cmdArgs = 6 + case "extracttweak": + cmdArgs = 2 + case "auditprivatecontract": + cmdArgs = 2 default: return fmt.Errorf("unknown command %v", args[0]), true } @@ -206,6 +314,10 @@ func run() (err error, showUsage bool) { return fmt.Errorf("unexpected argument: %s", flagset.Arg(0)), true } + if *simnetFlag { + chainParams = chaincfg.SimNetParams() + } + if *testnetFlag { chainParams = chaincfg.TestNet3Params() } @@ -213,11 +325,11 @@ func run() (err error, showUsage bool) { var cmd command switch args[0] { case "initiate": - cp2Addr, err := dcrutil.DecodeAddress(args[1], chainParams) + decodedAddr, err := stdaddr.DecodeAddress(args[1], chainParams) if err != nil { return fmt.Errorf("failed to decode participant address: %v", err), true } - _, ok := cp2Addr.(*dcrutil.AddressPubKeyHash) + cp2Addr, ok := decodedAddr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0) if !ok { return errors.New("participant address is not P2PKH"), true } @@ -234,11 +346,11 @@ func run() (err error, showUsage bool) { cmd = &initiateCmd{cp2Addr: cp2Addr, amount: amount} case "participate": - cp1Addr, err := dcrutil.DecodeAddress(args[1], chainParams) + decodedAddr, err := stdaddr.DecodeAddress(args[1], chainParams) if err != nil { return fmt.Errorf("failed to decode initiator address: %v", err), true } - _, ok := cp1Addr.(*dcrutil.AddressPubKeyHash) + cp1Addr, ok := decodedAddr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0) if !ok { return errors.New("initiator address is not P2PKH"), true } @@ -341,6 +453,211 @@ func run() (err error, showUsage bool) { } cmd = &auditContractCmd{contract: contract, contractTx: &contractTx} + + // The following commands are used for private swaps + + case "lockfunds": + decodedAddr, err := stdaddr.DecodeAddress(args[1], chainParams) + if err != nil { + return fmt.Errorf("failed to decode initiator address: %v", err), true + } + cpAddr, ok := decodedAddr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0) + if !ok { + return errors.New("initiator address is not P2PKH"), true + } + + amountF64, err := strconv.ParseFloat(args[2], 64) + if err != nil { + return fmt.Errorf("failed to decode amount: %v", err), true + } + amount, err := dcrutil.NewAmount(amountF64) + if err != nil { + return err, true + } + + initiator, err := strconv.ParseBool(args[3]) + if err != nil { + return fmt.Errorf("failed to decode initiator: %v", err), true + } + + cmd = &lockFundsCmd{ + cpAddr: cpAddr, + amount: amount, + initiator: initiator, + } + case "initiateadaptor": + ourContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode contract: %v", err), true + } + + ourLockTx, err := parseTx(args[2]) + if err != nil { + return err, true + } + + cpUnsignedRedeem, err := parseTx(args[3]) + if err != nil { + return err, true + } + + cmd = &initiateAdaptorCmd{ + ourContract: ourContract, + ourLockTx: ourLockTx, + cpUnsignedRedeemTx: cpUnsignedRedeem, + } + + case "verifyadaptor": + cpContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode contract: %v", err), true + } + + cpAdaptor, err := parseAdaptorSig(args[2]) + if err != nil { + return err, true + } + + cpPubKeyB, err := hex.DecodeString(args[3]) + if err != nil { + return fmt.Errorf("failed to decode contract transaction: %v", err), true + } + cpPubKey, err := secp256k1.ParsePubKey(cpPubKeyB) + if err != nil { + return fmt.Errorf("parse pub key: %v", err), true + } + + ourUnsignedRedeem, err := parseTx(args[4]) + if err != nil { + return err, true + } + + cmd = &verifyAdaptorCmd{ + cpContract: cpContract, + cpAdaptorSig: cpAdaptor, + cpPubKey: cpPubKey, + ourUnsignedRedeem: ourUnsignedRedeem, + } + case "participateadaptor": + ourContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode contract: %v", err), true + } + + ourLockTx, err := parseTx(args[2]) + if err != nil { + return err, true + } + + cpAdaptor, err := parseAdaptorSig(args[3]) + if err != nil { + return err, true + } + + cpUnsignedRedeem, err := parseTx(args[4]) + if err != nil { + return err, true + } + + cmd = &participateAdaptorCmd{ + cpAdaptor: cpAdaptor, + ourLockTx: ourLockTx, + ourContract: ourContract, + cpUnsignedRedemption: cpUnsignedRedeem, + } + case "unsignedredemption": + cpContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode contract: %v", err), true + } + + cpLockTx, err := parseTx(args[2]) + if err != nil { + return err, true + } + + cmd = &unsignedRedemptionCmd{ + cpContract: cpContract, + cpLockTx: cpLockTx, + } + + case "privateredeem": + cpContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode contract: %v", err), true + } + + cpLockTx, err := parseTx(args[2]) + if err != nil { + return err, true + } + + cpAdaptor, err := parseAdaptorSig(args[3]) + if err != nil { + return err, true + } + + cpPubKeyB, err := hex.DecodeString(args[4]) + if err != nil { + return err, true + } + cpPubKey, err := secp256k1.ParsePubKey(cpPubKeyB) + if err != nil { + return fmt.Errorf("parse pub key: %v", err), true + } + + unsignedRedemption, err := parseTx(args[5]) + if err != nil { + return err, true + } + + tweakBytes, err := hex.DecodeString(args[6]) + if err != nil { + return fmt.Errorf("failed to decode contract transaction: %v", err), true + } + var tweakBuf [32]byte + copy(tweakBuf[:], tweakBytes) + var tweak secp256k1.ModNScalar + tweak.SetBytes(&tweakBuf) + + cmd = &privateRedeemCmd{ + cpContract: cpContract, + cpLockTx: cpLockTx, + cpAdaptor: cpAdaptor, + cpPubKey: cpPubKey, + tweak: &tweak, + unsignedRedemption: unsignedRedemption, + } + case "extracttweak": + cpRedeemTx, err := parseTx(args[1]) + if err != nil { + return err, true + } + + ourAdaptor, err := parseAdaptorSig(args[2]) + if err != nil { + return err, true + } + + cmd = &extractTweakCmd{ + cpRedeemTx: cpRedeemTx, + ourAdaptor: ourAdaptor, + } + case "auditprivatecontract": + cpContract, err := hex.DecodeString(args[1]) + if err != nil { + return fmt.Errorf("failed to decode contract: %v", err), true + } + + cpLockTx, err := parseTx(args[2]) + if err != nil { + return err, true + } + + cmd = &auditPrivateContractCmd{ + cpContract: cpContract, + cpLockTx: cpLockTx, + } } // Offline commands don't need to talk to the wallet. @@ -362,10 +679,11 @@ func run() (err error, showUsage bool) { return fmt.Errorf("open client keypair: %v", err), false } tc := &tls.Config{ - Certificates: []tls.Certificate{keypair}, - RootCAs: x509.NewCertPool(), + Certificates: []tls.Certificate{keypair}, + RootCAs: x509.NewCertPool(), + InsecureSkipVerify: true, } - serverCAs, err := ioutil.ReadFile(*certFlag) + serverCAs, err := os.ReadFile(*certFlag) if err != nil { help := *certFlag == "" return fmt.Errorf("cannot open server certificate: %v", err), help @@ -449,17 +767,24 @@ func promptPublishTx(ctx context.Context, c pb.WalletServiceClient, tx []byte, n // contractArgs specifies the common parameters used to create the initiator's // and participant's contract. type contractArgs struct { - them dcrutil.Address + them *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0 amount dcrutil.Amount locktime int64 secretHash []byte } +type privateContractArgs struct { + us *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0 + them *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0 + amount dcrutil.Amount + locktime int64 +} + // builtContract houses the details regarding a contract and the contract // payment transaction, as well as the transaction to perform a refund. type builtContract struct { contract []byte - contractP2SH dcrutil.Address + contractP2SH stdaddr.Address contractTxHash *chainhash.Hash contractTx *wire.MsgTx contractFee dcrutil.Amount @@ -481,11 +806,12 @@ func buildContract(ctx context.Context, c pb.WalletServiceClient, args *contract if err != nil { return nil, err } - refundAddr, err := dcrutil.DecodeAddress(nar.Address, chainParams) + addr, err := stdaddr.DecodeAddress(nar.Address, chainParams) if err != nil { return nil, err } - if _, ok := refundAddr.(*dcrutil.AddressPubKeyHash); !ok { + refundAddr, ok := addr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0) + if !ok { return nil, fmt.Errorf("NextAddress: address %v is not P2PKH", refundAddr) } @@ -494,21 +820,18 @@ func buildContract(ctx context.Context, c pb.WalletServiceClient, args *contract if err != nil { return nil, err } - contractP2SH, err := dcrutil.NewAddressScriptHash(contract, chainParams) - if err != nil { - return nil, err - } - contractP2SHPkScript, err := txscript.PayToAddrScript(contractP2SH) + contractP2SH, err := stdaddr.NewAddressScriptHash(0, contract, chainParams) if err != nil { return nil, err } + scriptVersion, contractP2SHPkScript := contractP2SH.PaymentScript() ctr, err := c.ConstructTransaction(ctx, &pb.ConstructTransactionRequest{ SourceAccount: 0, // TODO NonChangeOutputs: []*pb.ConstructTransactionRequest_Output{{ Destination: &pb.ConstructTransactionRequest_OutputDestination{ Script: contractP2SHPkScript, - ScriptVersion: 0, + ScriptVersion: uint32(scriptVersion), }, Amount: int64(args.amount), }}, @@ -553,15 +876,11 @@ func buildRefund(ctx context.Context, c pb.WalletServiceClient, contract []byte, feePerKb dcrutil.Amount, passphrase []byte) ( refundTx *wire.MsgTx, refundFee dcrutil.Amount, err error) { - contractP2SH, err := dcrutil.NewAddressScriptHash(contract, chainParams) - if err != nil { - return nil, 0, err - } - contractP2SHPkScript, err := txscript.PayToAddrScript(contractP2SH) + contractP2SH, err := stdaddr.NewAddressScriptHash(0, contract, chainParams) if err != nil { return nil, 0, err } - + _, contractP2SHPkScript := contractP2SH.PaymentScript() contractTxHash := contractTx.TxHash() contractOutPoint := wire.OutPoint{Hash: contractTxHash, Index: ^uint32(0)} for i, o := range contractTx.TxOut { @@ -582,23 +901,18 @@ func buildRefund(ctx context.Context, c pb.WalletServiceClient, contract []byte, if err != nil { return nil, 0, err } - refundAddress, err := dcrutil.DecodeAddress(nar.Address, chainParams) - if err != nil { - return nil, 0, err - } - refundOutScript, err := txscript.PayToAddrScript(refundAddress) + refundAddress, err := stdaddr.DecodeAddress(nar.Address, chainParams) if err != nil { return nil, 0, err } - - pushes, err := txscript.ExtractAtomicSwapDataPushes(0, contract) - if err != nil { + _, refundOutScript := refundAddress.PaymentScript() + pushes := stdscript.ExtractAtomicSwapDataPushesV0(contract) + if pushes == nil { // expected to only be called with good input - panic(err) + panic("invalid atomic swap contract") } - refundAddr, err := dcrutil.NewAddressPubKeyHash(pushes.RefundHash160[:], chainParams, - dcrec.STEcdsaSecp256k1) + refundAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(0, pushes.RefundHash160[:], chainParams) if err != nil { return nil, 0, err } @@ -607,11 +921,9 @@ func buildRefund(ctx context.Context, c pb.WalletServiceClient, contract []byte, refundTx.LockTime = uint32(pushes.LockTime) refundTx.AddTxOut(wire.NewTxOut(0, refundOutScript)) // amount set below refundSize := estimateRefundSerializeSize(contract, refundTx.TxOut) - refundFee = txrules.FeeForSerializeSize(feePerKb, refundSize) + + refundFee = dcrutil.Amount(feePerKb * dcrutil.Amount(refundSize) / 1000) refundTx.TxOut[0].Value = contractTx.TxOut[contractOutPoint.Index].Value - int64(refundFee) - if txrules.IsDustOutput(refundTx.TxOut[0], feePerKb) { - return nil, 0, fmt.Errorf("refund output value of %v is dust", dcrutil.Amount(refundTx.TxOut[0].Value)) - } txIn := wire.NewTxIn(&contractOutPoint, 0, nil) txIn.Sequence = 0 @@ -623,7 +935,7 @@ func buildRefund(ctx context.Context, c pb.WalletServiceClient, contract []byte, refundSig, err := c.CreateSignature(ctx, &pb.CreateSignatureRequest{ Passphrase: passphrase, - Address: refundAddr.Address(), + Address: refundAddr.String(), SerializedTransaction: buf.Bytes(), InputIndex: 0, HashType: pb.CreateSignatureRequest_SIGHASH_ALL, @@ -659,52 +971,207 @@ func buildRefund(ctx context.Context, c pb.WalletServiceClient, contract []byte, return refundTx, refundFee, nil } -func sha256Hash(x []byte) []byte { - h := sha256.Sum256(x) - return h[:] -} - -func calcFeePerKb(absoluteFee dcrutil.Amount, serializeSize int) float64 { - return float64(absoluteFee) / float64(serializeSize) / 1e5 -} - -func (cmd *initiateCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { - var secret [secretSize]byte - _, err := rand.Read(secret[:]) +func buildPrivateContract(ctx context.Context, c pb.WalletServiceClient, args *privateContractArgs, passphrase []byte) (*builtContract, error) { + contract, err := privateAtomicSwapContract(*args.us.Hash160(), *args.them.Hash160(), args.locktime) if err != nil { - return err + return nil, err } - secretHash := sha256Hash(secret[:]) - // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted - // as a unix time rather than a block height. - locktime := time.Now().Add(48 * time.Hour).Unix() + contractP2SH, err := stdaddr.NewAddressScriptHash(0, contract, chainParams) + if err != nil { + return nil, err + } + scriptVer, contractP2SHPkScript := contractP2SH.PaymentScript() + ctr, err := c.ConstructTransaction(ctx, &pb.ConstructTransactionRequest{ + SourceAccount: 0, // TODO + NonChangeOutputs: []*pb.ConstructTransactionRequest_Output{{ + Destination: &pb.ConstructTransactionRequest_OutputDestination{ + Script: contractP2SHPkScript, + ScriptVersion: uint32(scriptVer), + }, + Amount: int64(args.amount), + }}, + }) + if err != nil { + return nil, err + } - passphrase, err := promptPassphase() + contractFee := dcrutil.Amount(ctr.TotalPreviousOutputAmount - ctr.TotalOutputAmount) + str, err := c.SignTransaction(ctx, &pb.SignTransactionRequest{ + Passphrase: passphrase, + SerializedTransaction: ctr.UnsignedTransaction, + }) if err != nil { - return err + return nil, err + } + var contractTx wire.MsgTx + err = contractTx.Deserialize(bytes.NewReader(str.Transaction)) + if err != nil { + return nil, err } - b, err := buildContract(ctx, c, &contractArgs{ - them: cmd.cp2Addr, - amount: cmd.amount, - locktime: locktime, - secretHash: secretHash, - }, passphrase) + refundTx, refundFee, err := buildPrivateRefund(ctx, c, contract, &contractTx, args.locktime, args.us, feePerKb, passphrase) if err != nil { - return err + return nil, err } - refundTxHash := b.refundTx.TxHash() - contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize()) - refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize()) + contractTxHash := contractTx.TxHash() + return &builtContract{ + contract, + contractP2SH, + &contractTxHash, + &contractTx, + contractFee, + refundTx, + refundFee, + }, nil +} - fmt.Printf("\n") - fmt.Printf("Secret: %x\n", secret) - fmt.Printf("Secret hash: %x\n\n", secretHash) - fmt.Printf("Contract fee: %v (%0.8f DCR/kB)\n", b.contractFee, contractFeePerKb) - fmt.Printf("Refund fee: %v (%0.8f DCR/kB)\n\n", b.refundFee, refundFeePerKb) - fmt.Printf("Contract (%v):\n", b.contractP2SH) +func buildPrivateRefund(ctx context.Context, c pb.WalletServiceClient, contract []byte, contractTx *wire.MsgTx, + locktime int64, redeemAddr *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0, feePerKb dcrutil.Amount, passphrase []byte) ( + refundTx *wire.MsgTx, refundFee dcrutil.Amount, err error) { + + contractP2SH, err := stdaddr.NewAddressScriptHash(scriptVersion, contract, chainParams) + if err != nil { + return nil, 0, err + } + _, contractP2SHPkScript := contractP2SH.PaymentScript() + + contractTxHash := contractTx.TxHash() + contractOutPoint := wire.OutPoint{Hash: contractTxHash, Index: ^uint32(0)} + for i, o := range contractTx.TxOut { + if bytes.Equal(o.PkScript, contractP2SHPkScript) { + contractOutPoint.Index = uint32(i) + break + } + } + if contractOutPoint.Index == ^uint32(0) { + return nil, 0, errors.New("contract tx does not contain a P2SH contract payment") + } + + nar, err := c.NextAddress(ctx, &pb.NextAddressRequest{ + Account: 0, // TODO + Kind: pb.NextAddressRequest_BIP0044_INTERNAL, + GapPolicy: pb.NextAddressRequest_GAP_POLICY_WRAP, + }) + if err != nil { + return nil, 0, err + } + refundAddress, err := stdaddr.DecodeAddress(nar.Address, chainParams) + if err != nil { + return nil, 0, err + } + _, refundOutScript := refundAddress.PaymentScript() + + refundTx = wire.NewMsgTx() + refundTx.LockTime = uint32(locktime) + refundTx.AddTxOut(wire.NewTxOut(0, refundOutScript)) // amount set below + refundSize := estimatePrivateRefundSerializeSize(contract, refundTx.TxOut) + + refundFee = feePerKb * dcrutil.Amount(refundSize) / 1000 + refundTx.TxOut[0].Value = contractTx.TxOut[contractOutPoint.Index].Value - int64(refundFee) + + txIn := wire.NewTxIn(&contractOutPoint, 0, nil) + txIn.Sequence = 0 + refundTx.AddTxIn(txIn) + + var buf bytes.Buffer + buf.Grow(refundTx.SerializeSize()) + err = refundTx.Serialize(&buf) + if err != nil { + return nil, 0, err + } + + privKeyBytes, err := dumpPrivateKey(ctx, c, redeemAddr.String()) + if err != nil { + return nil, 0, err + } + defer zeroBytes(privKeyBytes) + privKey := secp256k1.PrivKeyFromBytes(privKeyBytes) + defer privKey.Zero() + + sigHash, err := txscript.CalcSignatureHash(contract, txscript.SigHashAll, refundTx, int(0), nil) + if err != nil { + return nil, 0, err + } + + sig, err := schnorr.Sign(privKey, sigHash) + if err != nil { + return nil, 0, err + } + sigB := append(sig.Serialize(), byte(txscript.SigHashAll)) + refundSigScript, err := refundPrivateContract(contract, sigB, privKey.PubKey().SerializeCompressed()) + if err != nil { + return nil, 0, err + } + refundTx.TxIn[0].SignatureScript = refundSigScript + + sigCache, err := txscript.NewSigCache(10) + if err != nil { + return nil, 0, err + } + + if verify { + e, err := txscript.NewEngine(contractTx.TxOut[contractOutPoint.Index].PkScript, + refundTx, 0, verifyFlags, scriptVersion, sigCache) + if err != nil { + return nil, 0, err + } + err = e.Execute() + if err != nil { + return nil, 0, err + } + } + + return refundTx, refundFee, nil +} + +func sha256Hash(x []byte) []byte { + h := sha256.Sum256(x) + return h[:] +} + +func calcFeePerKb(absoluteFee dcrutil.Amount, serializeSize int) float64 { + return float64(absoluteFee) / float64(serializeSize) / 1e5 +} + +func (cmd *initiateCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + var secret [secretSize]byte + _, err := rand.Read(secret[:]) + if err != nil { + return err + } + secretHash := sha256Hash(secret[:]) + + // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted + // as a unix time rather than a block height. + locktime := time.Now().Add(48 * time.Hour).Unix() + + passphrase, err := promptPassphase() + if err != nil { + return err + } + + b, err := buildContract(ctx, c, &contractArgs{ + them: cmd.cp2Addr, + amount: cmd.amount, + locktime: locktime, + secretHash: secretHash, + }, passphrase) + if err != nil { + return err + } + + refundTxHash := b.refundTx.TxHash() + contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize()) + refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize()) + + fmt.Printf("\n") + fmt.Printf("Secret: %x\n", secret) + fmt.Printf("Secret hash: %x\n\n", secretHash) + fmt.Printf("Contract fee: %v (%0.8f DCR/kB)\n", b.contractFee, contractFeePerKb) + fmt.Printf("Refund fee: %v (%0.8f DCR/kB)\n\n", b.refundFee, refundFeePerKb) + fmt.Printf("Contract (%v):\n", b.contractP2SH) fmt.Printf("%x\n\n", b.contract) var contractBuf bytes.Buffer contractBuf.Grow(b.contractTx.SerializeSize()) @@ -720,6 +1187,354 @@ func (cmd *initiateCmd) runCommand(ctx context.Context, c pb.WalletServiceClient return promptPublishTx(ctx, c, contractBuf.Bytes(), "contract") } +func (cmd *lockFundsCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + passphrase, err := promptPassphase() + if err != nil { + return err + } + + nar, err := c.NextAddress(ctx, &pb.NextAddressRequest{ + Account: 0, // TODO + Kind: pb.NextAddressRequest_BIP0044_INTERNAL, + GapPolicy: pb.NextAddressRequest_GAP_POLICY_WRAP, + }) + if err != nil { + return err + } + decodedAddr, err := stdaddr.DecodeAddress(nar.Address, chainParams) + if err != nil { + return err + } + ourAddr, ok := decodedAddr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0) + if !ok { + return fmt.Errorf("NextAddress: address %v is not P2PKH", decodedAddr) + } + + var lockTime int64 + if cmd.initiator { + lockTime = time.Now().Add(48 * time.Hour).Unix() + } else { + lockTime = time.Now().Add(24 * time.Hour).Unix() + } + + b, err := buildPrivateContract(ctx, c, &privateContractArgs{ + us: ourAddr, + them: cmd.cpAddr, + amount: cmd.amount, + locktime: lockTime, + }, passphrase) + if err != nil { + return err + } + + contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize()) + refundTxHash := b.refundTx.TxHash() + refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize()) + + privateKey, err := dumpPrivateKey(ctx, c, ourAddr.String()) + if err != nil { + return err + } + defer zeroBytes(privateKey) + pubKey := secp256k1.PrivKeyFromBytes(privateKey).PubKey() + + fmt.Printf("\nContract fee: %v (%0.8f DCR/kB)\n", b.contractFee, contractFeePerKb) + fmt.Printf("Refund fee: %v (%0.8f DCR/kB)\n\n", b.refundFee, refundFeePerKb) + fmt.Printf("Contract (%v):\n", b.contractP2SH) + fmt.Printf("%x\n\n", b.contract) + fmt.Printf("Your pub key: %x\n\n", pubKey.SerializeCompressed()) + var contractBuf bytes.Buffer + contractBuf.Grow(b.contractTx.SerializeSize()) + b.contractTx.Serialize(&contractBuf) + fmt.Printf("Lock transaction (%v):\n", b.contractTxHash) + fmt.Printf("%x\n\n", contractBuf.Bytes()) + var refundBuf bytes.Buffer + refundBuf.Grow(b.refundTx.SerializeSize()) + b.refundTx.Serialize(&refundBuf) + fmt.Printf("Refund transaction (%v):\n", &refundTxHash) + fmt.Printf("%x\n\n", refundBuf.Bytes()) + + return promptPublishTx(ctx, c, contractBuf.Bytes(), "contract") +} + +func (cmd *unsignedRedemptionCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + nar, err := c.NextAddress(ctx, &pb.NextAddressRequest{ + Account: 0, // TODO + Kind: pb.NextAddressRequest_BIP0044_INTERNAL, + GapPolicy: pb.NextAddressRequest_GAP_POLICY_WRAP, + }) + if err != nil { + return err + } + addr, err := stdaddr.DecodeAddress(nar.Address, chainParams) + if err != nil { + return err + } + _, outScript := addr.PaymentScript() + + contractOutPoint, err := getContractOutPoint(cmd.cpLockTx, cmd.cpContract) + if err != nil { + return fmt.Errorf("contract transaction: %v", err) + } + + redeemTx := wire.NewMsgTx() + redeemTx.AddTxIn(wire.NewTxIn(contractOutPoint, 0, nil)) + redeemTx.AddTxOut(wire.NewTxOut(0, outScript)) // amount set below + redeemSize := estimatePrivateRedeemSerializeSize(cmd.cpContract, redeemTx.TxOut) + fee := feePerKb * dcrutil.Amount(redeemSize) / 1000 + redeemTx.TxOut[0].Value = cmd.cpLockTx.TxOut[contractOutPoint.Index].Value - int64(fee) + + var buf bytes.Buffer + buf.Grow(redeemTx.SerializeSize()) + redeemTx.Serialize(&buf) + redeemFeePerKb := calcFeePerKb(fee, redeemSize) + + fmt.Printf("\nRedeem Fee: %v (%0.8f DCR/kB)\n", fee, redeemFeePerKb) + fmt.Printf("Unsigned redemption tx bytes:\n%x\n", buf.Bytes()) + + return nil +} + +func (cmd *initiateAdaptorCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + ourPKH, _, _, err := extractPrivateAtomicSwapDetails(cmd.ourContract) + if err != nil { + return err + } + ourAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(ourPKH[:], chainParams) + if err != nil { + return err + } + + privKey, err := dumpPrivateKey(ctx, c, ourAddr.String()) + if err != nil { + return err + } + defer zeroBytes(privKey) + + sigB, err := sign.RawTxInSignature(cmd.cpUnsignedRedeemTx, 0, cmd.ourContract, txscript.SigHashAll, + privKey, dcrec.STSchnorrSecp256k1) + if err != nil { + return err + } + + var tBuf [32]byte + if _, err := rand.Read(tBuf[:]); err != nil { + return err + } + var tweak secp256k1.ModNScalar + tweak.SetBytes(&tBuf) + + sig, err := schnorr.ParseSignature(sigB[:64]) + if err != nil { + return err + } + adaptorSig := adaptor.PrivateKeyTweakedAdaptorSig(sig, secp256k1.PrivKeyFromBytes(privKey).PubKey(), &tweak) + if err != nil { + return err + } + + fmt.Printf("\nAdaptor Sig:\n%x\n", adaptorSig.Serialize()) + fmt.Printf("\nTweak:\n%x\n", tBuf) + + return nil +} + +func (cmd *verifyAdaptorCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + cpPKH, _, _, err := extractPrivateAtomicSwapDetails(cmd.cpContract) + if err != nil { + return err + } + expectedPKH := dcrutil.Hash160(cmd.cpPubKey.SerializeCompressed()) + if !bytes.Equal(cpPKH[:], expectedPKH) { + return fmt.Errorf("counterparty's pubkey does not match contract") + } + + ourRedemptionSigHash, err := txscript.CalcSignatureHash(cmd.cpContract, txscript.SigHashAll, cmd.ourUnsignedRedeem, int(0), nil) + if err != nil { + return err + } + if err := cmd.cpAdaptorSig.Verify(ourRedemptionSigHash, cmd.cpPubKey); err != nil { + return err + } + + fmt.Println("\nAdaptor sig is valid!") + return nil +} + +func (cmd *participateAdaptorCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + ourContractPKH, _, _, err := extractPrivateAtomicSwapDetails(cmd.ourContract) + if err != nil { + return err + } + ourContractAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion, ourContractPKH[:], chainParams) + if err != nil { + return err + } + + var buf bytes.Buffer + buf.Grow(cmd.ourLockTx.SerializeSize()) + cmd.ourLockTx.Serialize(&buf) + + privKey, err := dumpPrivateKey(ctx, c, ourContractAddr.String()) + if err != nil { + return err + } + defer zeroBytes(privKey) + + cpRedemptionSigHash, err := txscript.CalcSignatureHash(cmd.ourContract, txscript.SigHashAll, cmd.cpUnsignedRedemption, 0, nil) + if err != nil { + return err + } + + adaptorSig, err := adaptor.PublicKeyTweakedAdaptorSig(secp256k1.PrivKeyFromBytes(privKey), cpRedemptionSigHash, cmd.cpAdaptor.PublicTweak()) + if err != nil { + return err + } + + fmt.Printf("\nAdaptor Sig: %x\n", adaptorSig.Serialize()) + + return nil +} + +func (cmd *privateRedeemCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + _, ourPKH, _, err := extractPrivateAtomicSwapDetails(cmd.cpContract) + if err != nil { + return err + } + ourAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion, ourPKH[:], chainParams) + if err != nil { + return err + } + + hash, err := txscript.CalcSignatureHash(cmd.cpContract, txscript.SigHashAll, cmd.unsignedRedemption, 0, nil) + if err != nil { + return err + } + + cpSignature, err := cmd.cpAdaptor.Decrypt(cmd.tweak, hash) + if err != nil { + return err + } + + ourRedemptionSigHash, err := txscript.CalcSignatureHash(cmd.cpContract, txscript.SigHashAll, cmd.unsignedRedemption, int(0), nil) + if err != nil { + return err + } + + privKeyBytes, err := dumpPrivateKey(ctx, c, ourAddr.String()) + if err != nil { + return err + } + defer zeroBytes(privKeyBytes) + privKey := secp256k1.PrivKeyFromBytes(privKeyBytes) + defer privKey.Zero() + + ourRedeemSig, err := schnorr.Sign(privKey, ourRedemptionSigHash) + if err != nil { + return err + } + + ourSigB := append(ourRedeemSig.Serialize(), byte(txscript.SigHashAll)) + cpSigB := append(cpSignature.Serialize(), byte(txscript.SigHashAll)) + redeemContract, err := redeemPrivateContract(cmd.cpContract, privKey.PubKey().SerializeCompressed(), + ourSigB, cmd.cpPubKey.SerializeCompressed(), cpSigB) + if err != nil { + return err + } + + redeemTx := cmd.unsignedRedemption + redeemTx.TxIn[0].SignatureScript = redeemContract + + if verify { + op, err := getContractOutPoint(cmd.cpLockTx, cmd.cpContract) + if err != nil { + return err + } + sigCache, err := txscript.NewSigCache(10) + if err != nil { + return err + } + e, err := txscript.NewEngine(cmd.cpLockTx.TxOut[int(op.Index)].PkScript, + redeemTx, 0, verifyFlags, scriptVersion, sigCache) + if err != nil { + return err + } + err = e.Execute() + if err != nil { + return err + } + } + + return promptPublishTx(ctx, c, serializeTx(redeemTx), "redeem") +} + +func (cmd *extractTweakCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + _, _, _, sigThem, err := parseRedeemPrivateContract(cmd.cpRedeemTx.TxIn[0].SignatureScript) + if err != nil { + return err + } + sig, err := schnorr.ParseSignature(sigThem[:64]) + if err != nil { + return err + } + tweak, err := cmd.ourAdaptor.RecoverTweak(sig) + if err != nil { + return err + } + + tweakBytes := tweak.Bytes() + fmt.Printf("\nRecovered tweak:\n%x\n", tweakBytes[:]) + return nil +} + +func (cmd *auditPrivateContractCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { + creatorPKH, participantPKH, lockTime, err := extractPrivateAtomicSwapDetails(cmd.cpContract) + if err != nil { + return err + } + + creatorAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion, creatorPKH[:], chainParams) + if err != nil { + return err + } + + participantAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion, participantPKH[:], chainParams) + if err != nil { + return err + } + + contractP2SH, err := stdaddr.NewAddressScriptHash(scriptVersion, cmd.cpContract, chainParams) + if err != nil { + return err + } + + outpoint, err := getContractOutPoint(cmd.cpLockTx, cmd.cpContract) + if err != nil { + return err + } + output := cmd.cpLockTx.TxOut[int(outpoint.Index)] + + fmt.Printf("\nContract address: %v\n", contractP2SH.String()) + fmt.Printf("Contract value: %v\n", dcrutil.Amount(output.Value)) + fmt.Printf("Creator address: %v\n", creatorAddr.String()) + fmt.Printf("Participant address: %v\n", participantAddr.String()) + + if lockTime >= int64(txscript.LockTimeThreshold) { + t := time.Unix(lockTime, 0) + fmt.Printf("Locktime: %v\n", t.UTC()) + reachedAt := time.Until(t).Truncate(time.Second) + if reachedAt > 0 { + fmt.Printf("Locktime reached in %v\n", reachedAt) + } else { + fmt.Printf("Contract refund time lock has expired\n") + } + } else { + fmt.Printf("Locktime: block %v\n", lockTime) + } + + return nil +} + func (cmd *participateCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted // as a unix time rather than a block height. @@ -764,25 +1579,25 @@ func (cmd *participateCmd) runCommand(ctx context.Context, c pb.WalletServiceCli } func (cmd *redeemCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { - pushes, err := txscript.ExtractAtomicSwapDataPushes( - scriptVersion, cmd.contract) - if err != nil { - return err + pushes := stdscript.ExtractAtomicSwapDataPushesV0(cmd.contract) + if pushes == nil { + return fmt.Errorf("invalid atomic swap script") } if pushes == nil { return errors.New("contract is not an atomic swap script recognized by this tool") } - recipientAddr, err := dcrutil.NewAddressPubKeyHash(pushes.RecipientHash160[:], - chainParams, dcrec.STEcdsaSecp256k1) + recipientAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion, pushes.RecipientHash160[:], chainParams) if err != nil { return err } - contractHash := dcrutil.Hash160(cmd.contract) + contractHash := stdaddr.Hash160(cmd.contract) contractOut := -1 for i, out := range cmd.contractTx.TxOut { - sc, addrs, _, _ := txscript.ExtractPkScriptAddrs(out.Version, out.PkScript, - chainParams, isTreasuryEnabled) - if sc == txscript.ScriptHashTy && bytes.Equal(addrs[0].Hash160()[:], contractHash) { + scriptHash := txscript.ExtractScriptHash(out.PkScript) + if scriptHash == nil { + continue + } + if bytes.Equal(scriptHash, contractHash) { contractOut = i break } @@ -799,14 +1614,11 @@ func (cmd *redeemCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) if err != nil { return err } - addr, err := dcrutil.DecodeAddress(nar.Address, chainParams) - if err != nil { - return err - } - outScript, err := txscript.PayToAddrScript(addr) + addr, err := stdaddr.DecodeAddress(nar.Address, chainParams) if err != nil { return err } + _, outScript := addr.PaymentScript() contractTxHash := cmd.contractTx.TxHash() contractOutPoint := wire.OutPoint{ @@ -837,7 +1649,7 @@ func (cmd *redeemCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) redeemSig, err := c.CreateSignature(ctx, &pb.CreateSignatureRequest{ Passphrase: passphrase, - Address: recipientAddr.Address(), + Address: recipientAddr.String(), SerializedTransaction: buf.Bytes(), InputIndex: 0, HashType: pb.CreateSignatureRequest_SIGHASH_ALL, @@ -854,7 +1666,7 @@ func (cmd *redeemCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) redeemTx.TxIn[0].SignatureScript = redeemSigScript redeemTxHash := redeemTx.TxHash() - redeemFeePerKb := calcFeePerKb(fee, redeemTx.SerializeSize()) + redeemFeePerKb := calcFeePerKb(dcrutil.Amount(fee.ToCoin()), redeemTx.SerializeSize()) buf.Reset() buf.Grow(redeemTx.SerializeSize()) @@ -885,10 +1697,9 @@ func (cmd *redeemCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) } func (cmd *refundCmd) runCommand(ctx context.Context, c pb.WalletServiceClient) error { - pushes, err := txscript.ExtractAtomicSwapDataPushes( - scriptVersion, cmd.contract) - if err != nil { - return err + pushes := stdscript.ExtractAtomicSwapDataPushesV0(cmd.contract) + if pushes == nil { + return fmt.Errorf("invalid atomic swap script") } if pushes == nil { return errors.New("contract is not an atomic swap script recognized by this tool") @@ -929,13 +1740,11 @@ func (cmd *extractSecretCmd) runOfflineCommand() error { // contract with some "nonstandard" or unrecognized transaction or script // type. for _, in := range cmd.redemptionTx.TxIn { - pushes, err := txscript.PushedData(in.SignatureScript) - if err != nil { - return err - } - for _, push := range pushes { - if bytes.Equal(sha256Hash(push), cmd.secretHash) { - fmt.Printf("Secret: %x\n", push) + tokenizer := txscript.MakeScriptTokenizer(scriptVersion, in.SignatureScript) + for tokenizer.Next() { + data := tokenizer.Data() + if data != nil && bytes.Equal(sha256Hash(data), cmd.secretHash) { + fmt.Printf("Secret: %x\n", data) return nil } } @@ -951,12 +1760,11 @@ func (cmd *auditContractCmd) runOfflineCommand() error { contractHash160 := dcrutil.Hash160(cmd.contract) contractOut := -1 for i, out := range cmd.contractTx.TxOut { - sc, addrs, _, err := txscript.ExtractPkScriptAddrs(out.Version, out.PkScript, - chainParams, isTreasuryEnabled) - if err != nil || sc != txscript.ScriptHashTy { + scriptHash := txscript.ExtractScriptHash(out.PkScript) + if scriptHash == nil { continue } - if bytes.Equal(addrs[0].(*dcrutil.AddressScriptHash).Hash160()[:], contractHash160) { + if bytes.Equal(scriptHash, contractHash160) { contractOut = i break } @@ -965,9 +1773,9 @@ func (cmd *auditContractCmd) runOfflineCommand() error { return errors.New("transaction does not contain the contract output") } - pushes, err := txscript.ExtractAtomicSwapDataPushes(scriptVersion, cmd.contract) - if err != nil { - return err + pushes := stdscript.ExtractAtomicSwapDataPushesV0(cmd.contract) + if pushes == nil { + return fmt.Errorf("invalid atomic swap script") } if pushes == nil { return errors.New("contract is not an atomic swap script recognized by this tool") @@ -976,17 +1784,15 @@ func (cmd *auditContractCmd) runOfflineCommand() error { return fmt.Errorf("contract specifies strange secret size %v", pushes.SecretSize) } - contractAddr, err := dcrutil.NewAddressScriptHash(cmd.contract, chainParams) + contractAddr, err := stdaddr.NewAddressScriptHash(scriptVersion, cmd.contract, chainParams) if err != nil { return err } - recipientAddr, err := dcrutil.NewAddressPubKeyHash(pushes.RecipientHash160[:], - chainParams, dcrec.STEcdsaSecp256k1) + recipientAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion, pushes.RecipientHash160[:], chainParams) if err != nil { return err } - refundAddr, err := dcrutil.NewAddressPubKeyHash(pushes.RefundHash160[:], - chainParams, dcrec.STEcdsaSecp256k1) + refundAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion, pushes.RefundHash160[:], chainParams) if err != nil { return err } @@ -1017,9 +1823,9 @@ func (cmd *auditContractCmd) runOfflineCommand() error { // atomicSwapContract returns an output script that may be redeemed by one of // two signature scripts: // -// 1 +// 1 // -// 0 +// 0 // // The first signature script is the normal redemption path done by the other // party and requires the initiator's secret. The second signature script is @@ -1073,6 +1879,113 @@ func atomicSwapContract(pkhMe, pkhThem *[ripemd160.Size]byte, locktime int64, se return b.Script() } +func privateAtomicSwapContract(us, them [ripemd160.Size]byte, locktime int64) ([]byte, error) { + b := txscript.NewScriptBuilder() + + b.AddOp(txscript.OP_IF) // Normal redeem path + { + b.AddOp(txscript.OP_DUP) + b.AddOp(txscript.OP_HASH160) + b.AddData(them[:]) + b.AddOp(txscript.OP_EQUALVERIFY) + b.AddOp(txscript.OP_2) + b.AddOp(txscript.OP_CHECKSIGALTVERIFY) + } + b.AddOp(txscript.OP_ELSE) // Refund path + { + b.AddInt64(locktime) + b.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + b.AddOp(txscript.OP_DROP) + } + b.AddOp(txscript.OP_ENDIF) + b.AddOp(txscript.OP_DUP) + b.AddOp(txscript.OP_HASH160) + b.AddData(us[:]) + b.AddOp(txscript.OP_EQUALVERIFY) + b.AddOp(txscript.OP_2) + b.AddOp(txscript.OP_CHECKSIGALT) + + script, err := b.Script() + if err != nil { + return nil, err + } + + return script, nil +} + +func extractPrivateAtomicSwapDetails(script []byte) (creator, participant [ripemd160.Size]byte, locktime int64, err error) { + if len(script) != 62 { + err = fmt.Errorf("invalid swap contract: length = %v", len(script)) + return + } + + if script[0] == txscript.OP_IF && + script[1] == txscript.OP_DUP && + script[2] == txscript.OP_HASH160 && + script[3] == txscript.OP_DATA_20 && + // creator's (20 bytes) + script[24] == txscript.OP_EQUALVERIFY && + script[25] == txscript.OP_2 && + script[26] == txscript.OP_CHECKSIGALTVERIFY && + script[27] == txscript.OP_ELSE && + script[28] == txscript.OP_DATA_4 && + // lockTime (4 bytes) + script[33] == txscript.OP_CHECKLOCKTIMEVERIFY && + script[34] == txscript.OP_DROP && + script[35] == txscript.OP_ENDIF && + script[36] == txscript.OP_DUP && + script[37] == txscript.OP_HASH160 && + script[38] == txscript.OP_DATA_20 && + + // participant's pkh (20 bytes) + script[59] == txscript.OP_EQUALVERIFY && + script[60] == txscript.OP_2 && + script[61] == txscript.OP_CHECKSIGALT { + copy(participant[:], script[4:24]) + copy(creator[:], script[39:59]) + locktime = int64(binary.LittleEndian.Uint32(script[29:33])) + } else { + err = fmt.Errorf("invalid swap contract") + } + + return +} + +func redeemPrivateContract(contract, pkUs, sigUs, pkThem, sigThem []byte) ([]byte, error) { + b := txscript.NewScriptBuilder() + + b.AddData(sigThem) + b.AddData(pkThem) + b.AddData(sigUs) + b.AddData(pkUs) + b.AddInt64(1) + b.AddData(contract) + return b.Script() +} + +func parseRedeemPrivateContract(script []byte) (pkUs, sigUs, pkThem, sigThem []byte, err error) { + if len(script) < redeemPrivateAtomicSwapSigScriptSize { + err = fmt.Errorf("invalid swap redemption: length = %v", len(script)) + return + } + + sigThem = script[1:66] + pkThem = script[67:100] + sigUs = script[101:167] + pkUs = script[168:201] + + return +} + +func refundPrivateContract(contract, sig, pubkey []byte) ([]byte, error) { + b := txscript.NewScriptBuilder() + b.AddData(sig) + b.AddData(pubkey) + b.AddInt64(0) + b.AddData(contract) + return b.Script() +} + // redeemP2SHContract returns the signature script to redeem a contract output // using the redeemer's signature and the initiator's secret. This function // assumes P2SH and appends the contract as the final data push. @@ -1097,3 +2010,53 @@ func refundP2SHContract(contract, sig, pubkey []byte) ([]byte, error) { b.AddData(contract) return b.Script() } + +func dumpPrivateKey(ctx context.Context, c pb.WalletServiceClient, address string) ([]byte, error) { + privateKey, err := c.DumpPrivateKey(ctx, &pb.DumpPrivateKeyRequest{ + Address: address, + }) + if err != nil { + return nil, err + } + defer func() { privateKey.PrivateKeyWif = "" }() + + wif, err := dcrutil.DecodeWIF(privateKey.PrivateKeyWif, chainParams.PrivateKeyID) + if err != nil { + return nil, err + } + + return wif.PrivKey(), nil +} + +func zeroBytes(b []byte) { + for i := range b { + b[i] = 0 + } +} + +func getContractOutPoint(tx *wire.MsgTx, contract []byte) (*wire.OutPoint, error) { + contractAddr, err := stdaddr.NewAddressScriptHash(scriptVersion, contract, chainParams) + if err != nil { + return nil, err + } + _, script := contractAddr.PaymentScript() + contractTxHash := tx.TxHash() + contractOutPoint := wire.OutPoint{Hash: contractTxHash, Index: ^uint32(0)} + for i, o := range tx.TxOut { + if bytes.Equal(o.PkScript, script) { + contractOutPoint.Index = uint32(i) + break + } + } + if contractOutPoint.Index == ^uint32(0) { + return nil, errors.New("contract tx does not contain a P2SH contract payment") + } + return &contractOutPoint, nil +} + +func serializeTx(tx *wire.MsgTx) []byte { + var buf bytes.Buffer + buf.Grow(tx.SerializeSize()) + tx.Serialize(&buf) + return buf.Bytes() +} diff --git a/cmd/dcratomicswap/sizeest.go b/cmd/dcratomicswap/sizeest.go index 7b42d62..b37e1bd 100644 --- a/cmd/dcratomicswap/sizeest.go +++ b/cmd/dcratomicswap/sizeest.go @@ -6,7 +6,7 @@ package main import ( - "github.com/decred/dcrd/txscript/v3" + "github.com/decred/dcrd/txscript/v4" "github.com/decred/dcrd/wire" ) @@ -35,6 +35,33 @@ const ( // - 33 bytes serialized compressed pubkey // - OP_FALSE refundAtomicSwapSigScriptSize = 1 + 73 + 1 + 33 + 1 + + // redeemPrivateAtomicSwapSigScriptSize is the worst case (largest) serialize + // size of a transaction input script to redeem a private atomic swap contract. + // This does not include final push for the contract itself. + // + // - OP_DATA_65 + // - 64 bytes schnorr signature + 1 byte sighash + // - OP_DATA_33 + // - 33 bytes serialized compressed pubkey + // - OP_DATA_65 + // - 64 bytes schnorr signature + 1 byte sighash + // - OP_DATA_33 + // - 33 bytes serialized compressed pubkey + // - OP_TRUE + redeemPrivateAtomicSwapSigScriptSize = 1 + 65 + 1 + 33 + 1 + 65 + 1 + 33 + 1 + + // refundPrivateAtomicSwapSigScriptSize is the worst case (largest) + // serialize size of a transaction input script that refunds a private + // atomic swap contract. This does not include final push for the + // contract itself. + // + // - OP_DATA_65 + // - 64 bytes schnorr signature + 1 byte sighash + // - OP_DATA_33 + // - 33 bytes serialized compressed pubkey + // - OP_FALSE + refundPrivateAtomicSwapSigScriptSize = 1 + 65 + 1 + 33 + 1 ) func sumOutputSerializeSizes(outputs []*wire.TxOut) (serializeSize int) { @@ -93,3 +120,37 @@ func estimateRefundSerializeSize(contract []byte, txOuts []*wire.TxOut) int { inputSize(refundAtomicSwapSigScriptSize+contractPushSize) + sumOutputSerializeSizes(txOuts) } + +// estimatePrivateRedeemSerializeSize returns a worst case serialize size +// estimate for a transaction that redeems a private atomic swap P2SH output. +func estimatePrivateRedeemSerializeSize(contract []byte, txOuts []*wire.TxOut) int { + contractPush, err := txscript.NewScriptBuilder().AddData(contract).Script() + if err != nil { + // Should never be hit since this script does exceed the limits. + panic(err) + } + contractPushSize := len(contractPush) + + // 12 additional bytes are for version, locktime and expiry. + return 12 + (2 * wire.VarIntSerializeSize(1)) + + wire.VarIntSerializeSize(1) + + inputSize(redeemPrivateAtomicSwapSigScriptSize+contractPushSize) + + sumOutputSerializeSizes(txOuts) +} + +// estimatePrivateRefundSerializeSize returns a worst case serialize size +// estimate for a transaction that refunds a private atomic swap P2SH output. +func estimatePrivateRefundSerializeSize(contract []byte, txOuts []*wire.TxOut) int { + contractPush, err := txscript.NewScriptBuilder().AddData(contract).Script() + if err != nil { + // Should never be hit since this script does exceed the limits. + panic(err) + } + contractPushSize := len(contractPush) + + // 12 additional bytes are for version, locktime and expiry. + return 12 + (2 * wire.VarIntSerializeSize(1)) + + wire.VarIntSerializeSize(1) + + inputSize(refundPrivateAtomicSwapSigScriptSize+contractPushSize) + + sumOutputSerializeSizes(txOuts) +}