From 379bc04276ab4c558e166b9eb3c576480a5a19db Mon Sep 17 00:00:00 2001 From: Eugene Siegel Date: Thu, 18 Jul 2024 19:47:51 -0400 Subject: [PATCH 1/4] ellswift: introduce ElligatorSwift encoding and decoding funcs The BIP324 ElligatorSwift test vectors are also included. --- btcec/ellswift.go | 392 ++++++++++++++++++++ btcec/ellswift_test.go | 819 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1211 insertions(+) create mode 100644 btcec/ellswift.go create mode 100644 btcec/ellswift_test.go diff --git a/btcec/ellswift.go b/btcec/ellswift.go new file mode 100644 index 0000000000..7bc678c735 --- /dev/null +++ b/btcec/ellswift.go @@ -0,0 +1,392 @@ +package btcec + +import ( + "crypto/rand" + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +var ( + // c is sqrt(-3) (mod p) + c FieldVal + + cBytes = [32]byte{ + 0x0a, 0x2d, 0x2b, 0xa9, 0x35, 0x07, 0xf1, 0xdf, + 0x23, 0x37, 0x70, 0xc2, 0xa7, 0x97, 0x96, 0x2c, + 0xc6, 0x1f, 0x6d, 0x15, 0xda, 0x14, 0xec, 0xd4, + 0x7d, 0x8d, 0x27, 0xae, 0x1c, 0xd5, 0xf8, 0x52, + } + + ellswiftTag = []byte("bip324_ellswift_xonly_ecdh") + + // ErrPointNotOnCurve is returned when we're unable to find a point on the + // curve. + ErrPointNotOnCurve = fmt.Errorf("point does not exist on secp256k1 curve") +) + +func init() { + c.SetByteSlice(cBytes[:]) +} + +// XSwiftEC() takes two field elements (u, t) and gives us an x-coordinate that +// is on the secp256k1 curve. This is used to take an ElligatorSwift-encoded +// public key (u, t) and return the point on the curve it maps to. +// TODO: Rewrite these so to avoid new(FieldVal).Add(...) usage? +// NOTE: u, t MUST be normalized. The result x is normalized. +func XSwiftEC(u, t *FieldVal) *FieldVal { + // 1. Let u' = u if u != 0, else = 1 + if u.IsZero() { + u.SetInt(1) + } + + // 2. Let t' = t if t != 0, else 1 + if t.IsZero() { + t.SetInt(1) + } + + // 3. Let t'' = t' if g(u') != -(t'^2); t'' = 2t' otherwise + // g(x) = x^3 + ax + b, a = 0, b = 7 + + // Calculate g(u'). + gu := new(FieldVal).SquareVal(u).Mul(u).AddInt(7).Normalize() + + // Calculate the right-hand side of the equation (-t'^2) + rhs := new(FieldVal).SquareVal(t).Negate(1).Normalize() + + if gu.Equals(rhs) { + // t'' = 2t' + t = t.Add(t) + } + + // 4. X = (u'^3 + b - t''^2) / (2t'') + tSquared := new(FieldVal).SquareVal(t).Negate(1) + xNum := new(FieldVal).SquareVal(u).Mul(u).AddInt(7).Add(tSquared) + xDenom := new(FieldVal).Add2(t, t).Inverse() + x := xNum.Mul(xDenom) + + // 5. Y = (X+t'') / (u' * c) + yNum := new(FieldVal).Add2(x, t) + yDenom := new(FieldVal).Mul2(u, &c).Inverse() + y := yNum.Mul(yDenom) + + // 6. Return the first x in (u'+4Y^2, -X/2Y - u'/2, X/2Y - u'/2) for which + // x^3 + b is square. + + // 6a. Calculate u' +4Y^2 and determine if x^3+7 is square. + ySqr := new(FieldVal).Add(y).Mul(y) + quadYSqr := new(FieldVal).Add(ySqr).MulInt(4) + firstX := new(FieldVal).Add(u).Add(quadYSqr) + + firstXCurve := new(FieldVal).Add(firstX).Square().Mul(firstX).AddInt(7) + + // Now determine if firstXCurve is square (on the curve). + if new(FieldVal).SquareRootVal(firstXCurve) { + return firstX.Normalize() + } + + // 6b. Calculate -X/2Y - u'/2 and determine if x^3 + 7 is square + doubleYInv := new(FieldVal).Add(y).Add(y).Inverse() + xDivDoubleYInv := new(FieldVal).Add(x).Mul(doubleYInv) + negXDivDoubleYInv := new(FieldVal).Add(xDivDoubleYInv).Negate(1) + invTwo := new(FieldVal).AddInt(2).Inverse() + negUDivTwo := new(FieldVal).Add(u).Mul(invTwo).Negate(1) + secondX := new(FieldVal).Add(negXDivDoubleYInv).Add(negUDivTwo) + + secondXCurve := new(FieldVal).Add(secondX).Square().Mul(secondX).AddInt(7) + + // Now determine if secondXCurve is square. + if new(FieldVal).SquareRootVal(secondXCurve) { + return secondX.Normalize() + } + + // 6c. Calculate X/2Y -u'/2 and determine if x^3 + 7 is square + thirdX := new(FieldVal).Add(xDivDoubleYInv).Add(negUDivTwo) + + thirdXCurve := new(FieldVal).Add(thirdX).Square().Mul(thirdX).AddInt(7) + + // Now determine if thirdXCurve is square. + if new(FieldVal).SquareRootVal(thirdXCurve) { + return thirdX.Normalize() + } + + // Should have found a square above. + panic("unreachable - no calculated x-values were square") +} + +// XSwiftECInv takes two field elements (u, x) (where x is on the curve) and +// returns a field element t. This is used to take a random field element u and +// a point on the curve and return a field element t where (u, t) forms the +// ElligatorSwift encoding. +// TODO: Rewrite these so to avoid new(FieldVal).Add(...) usage? +// NOTE: u, x MUST be normalized. The result `t` is normalized. +func XSwiftECInv(u, x *FieldVal, caseNum int) *FieldVal { + v := new(FieldVal) + s := new(FieldVal) + twoInv := new(FieldVal).AddInt(2).Inverse() + + if caseNum&2 == 0 { + // If lift_x(-x-u) succeeds, return None + if _, found := liftX(new(FieldVal).Add(x).Add(u).Negate(2)); found { + return nil + } + + // Let v = x + v.Add(x) + + // Let s = -(u^3+7)/(u^2 + uv + v^2) + uSqr := new(FieldVal).Add(u).Square() + vSqr := new(FieldVal).Add(v).Square() + sDenom := new(FieldVal).Add(u).Mul(v).Add(uSqr).Add(vSqr) + sNum := new(FieldVal).Add(uSqr).Mul(u).AddInt(7) + + s = sDenom.Inverse().Mul(sNum).Negate(1) + } else { + // Let s = x - u + negU := new(FieldVal).Add(u).Negate(1) + s.Add(x).Add(negU).Normalize() + + // If s = 0, return None + if s.IsZero() { + return nil + } + + // Let r be the square root of -s(4(u^3 + 7) + 3u^2s) + uSqr := new(FieldVal).Add(u).Square() + lhs := new(FieldVal).Add(uSqr).Mul(u).AddInt(7).MulInt(4) + rhs := new(FieldVal).Add(uSqr).MulInt(3).Mul(s) + + // Add the two terms together and multiply by -s. + lhs.Add(rhs).Normalize().Mul(s).Negate(1) + + r := new(FieldVal) + if !r.SquareRootVal(lhs) { + // If no square root was found, return None. + return nil + } + + if caseNum&1 == 1 && r.Normalize().IsZero() { + // If case & 1 = 1 and r = 0, return None. + return nil + } + + // Let v = (r/s - u)/2 + sInv := new(FieldVal).Add(s).Inverse() + uNeg := new(FieldVal).Add(u).Negate(1) + + v.Add(r).Mul(sInv).Add(uNeg).Mul(twoInv) + } + + w := new(FieldVal) + + if !w.SquareRootVal(s) { + // If no square root was found, return None. + return nil + } + + switch caseNum & 5 { + case 0: + // If case & 5 = 0, return -w(u(1-c)/2 + v) + oneMinusC := new(FieldVal).Add(&c).Negate(1).AddInt(1) + t := new(FieldVal).Add(u).Mul(oneMinusC).Mul(twoInv).Add(v).Mul(w). + Negate(1).Normalize() + + return t + + case 1: + // If case & 5 = 1, return w(u(1+c)/2 + v) + onePlusC := new(FieldVal).Add(&c).AddInt(1) + t := new(FieldVal).Add(u).Mul(onePlusC).Mul(twoInv).Add(v).Mul(w). + Normalize() + + return t + + case 4: + // If case & 5 = 4, return w(u(1-c)/2 + v) + oneMinusC := new(FieldVal).Add(&c).Negate(1).AddInt(1) + t := new(FieldVal).Add(u).Mul(oneMinusC).Mul(twoInv).Add(v).Mul(w). + Normalize() + + return t + + case 5: + // If case & 5 = 5, return -w(u(1+c)/2 + v) + onePlusC := new(FieldVal).Add(&c).AddInt(1) + t := new(FieldVal).Add(u).Mul(onePlusC).Mul(twoInv).Add(v).Mul(w). + Negate(1).Normalize() + + return t + } + + panic("should not reach here") +} + +// XElligatorSwift takes the x-coordinate of a point on secp256k1 and generates +// ElligatorSwift encoding of that point composed of two field elements (u, t). +// NOTE: x MUST be normalized. The return values u, t are normalized. +func XElligatorSwift(x *FieldVal) (*FieldVal, *FieldVal, error) { + // We'll choose a random `u` value and a random case so that we can + // generate a `t` value. + for { + // Choose random u value. + var randUBytes [32]byte + _, err := rand.Read(randUBytes[:]) + if err != nil { + return nil, nil, err + } + + u := new(FieldVal) + overflow := u.SetBytes(&randUBytes) + if overflow == 1 { + u.Normalize() + } + + // Choose a random case in the interval [0, 7] + var randCaseByte [1]byte + _, err = rand.Read(randCaseByte[:]) + if err != nil { + return nil, nil, err + } + + caseNum := randCaseByte[0] & 7 + + // Find t, if none is found, continue with the loop. + t := XSwiftECInv(u, x, int(caseNum)) + if t != nil { + return u, t, nil + } + } +} + +// EllswiftCreate generates a random private key and returns that along with +// the ElligatorSwift encoding of its corresponding public key. +func EllswiftCreate() (*PrivateKey, [64]byte, error) { + var randPrivKeyBytes [64]byte + + // Generate a random private key + _, err := rand.Read(randPrivKeyBytes[:]) + if err != nil { + return nil, [64]byte{}, err + } + + privKey, _ := PrivKeyFromBytes(randPrivKeyBytes[:]) + + // Fetch the x-coordinate of the public key. + x := getXCoord(privKey) + + // Get the ElligatorSwift encoding of the public key. + u, t, err := XElligatorSwift(x) + if err != nil { + return nil, [64]byte{}, err + } + + uBytes := u.Bytes() + tBytes := t.Bytes() + + // ellswift_pub = bytes(u) || bytes(t), its encoding as 64 bytes + var ellswiftPub [64]byte + copy(ellswiftPub[0:32], (*uBytes)[:]) + copy(ellswiftPub[32:64], (*tBytes)[:]) + + // Return (priv, ellswift_pub) + return privKey, ellswiftPub, nil +} + +// EllswiftECDHXOnly takes the ElligatorSwift-encoded public key of a +// counter-party and performs ECDH with our private key. +func EllswiftECDHXOnly(ellswiftTheirs [64]byte, privKey *PrivateKey) ([32]byte, + error) { + + // Let u = int(ellswift_theirs[:32]) mod p. + // Let t = int(ellswift_theirs[32:]) mod p. + uBytesTheirs := ellswiftTheirs[0:32] + tBytesTheirs := ellswiftTheirs[32:64] + + var uTheirs FieldVal + overflow := uTheirs.SetByteSlice(uBytesTheirs[:]) + if overflow { + uTheirs.Normalize() + } + + var tTheirs FieldVal + overflow = tTheirs.SetByteSlice(tBytesTheirs[:]) + if overflow { + tTheirs.Normalize() + } + + // Calculate bytes(x(privâ‹…lift_x(XSwiftEC(u, t)))) + xTheirs := XSwiftEC(&uTheirs, &tTheirs) + pubKey, found := liftX(xTheirs) + if !found { + return [32]byte{}, ErrPointNotOnCurve + } + + var pubJacobian JacobianPoint + pubKey.AsJacobian(&pubJacobian) + + var sharedPoint JacobianPoint + ScalarMultNonConst(&privKey.Key, &pubJacobian, &sharedPoint) + sharedPoint.ToAffine() + + return *sharedPoint.X.Bytes(), nil +} + +// getXCoord fetches the corresponding public key's x-coordinate given a +// private key. +func getXCoord(privKey *PrivateKey) *FieldVal { + var result JacobianPoint + ScalarBaseMultNonConst(&privKey.Key, &result) + result.ToAffine() + return &result.X +} + +// liftX returns the point P with x-coordinate `x` and even y-coordinate. If a +// point exists on the curve, it returns true and false otherwise. +// TODO: Use quadratic residue formula instead (see: BIP340)? +func liftX(x *FieldVal) (*PublicKey, bool) { + ySqr := new(FieldVal).Add(x).Square().Mul(x).AddInt(7) + + y := new(FieldVal) + if !y.SquareRootVal(ySqr) { + // If we've reached here, the point does not exist on the curve. + return nil, false + } + + if !y.Normalize().IsOdd() { + return NewPublicKey(x, y), true + } + + // Negate y if it's odd. + if !y.Negate(1).Normalize().IsOdd() { + return NewPublicKey(x, y), true + } + + return nil, false +} + +// V2Ecdh performs x-only ecdh and returns a shared secret composed of a tagged +// hash which itself is composed of two ElligatorSwift-encoded public keys and +// the x-only ecdh point. +func V2Ecdh(priv *PrivateKey, ellswiftTheirs, ellswiftOurs [64]byte, + initiating bool) (*chainhash.Hash, error) { + + ecdhPoint, err := EllswiftECDHXOnly(ellswiftTheirs, priv) + if err != nil { + return nil, err + } + + if initiating { + // Initiating, place our public key encoding first. + var msg []byte + msg = append(msg, ellswiftOurs[:]...) + msg = append(msg, ellswiftTheirs[:]...) + msg = append(msg, ecdhPoint[:]...) + return chainhash.TaggedHash(ellswiftTag, msg), nil + } + + var msg []byte + msg = append(msg, ellswiftTheirs[:]...) + msg = append(msg, ellswiftOurs[:]...) + msg = append(msg, ecdhPoint[:]...) + return chainhash.TaggedHash(ellswiftTag, msg), nil +} diff --git a/btcec/ellswift_test.go b/btcec/ellswift_test.go new file mode 100644 index 0000000000..94fa6fbd2d --- /dev/null +++ b/btcec/ellswift_test.go @@ -0,0 +1,819 @@ +package btcec + +import ( + "testing" +) + +// TestXSwiftECVectors checks the BIP324 test vectors for the XSwiftEC function. +func TestXSwiftECVectors(t *testing.T) { + tests := []struct { + ellswift string + expectedX string + }{ + { + ellswift: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedX: "edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c", + }, + { + ellswift: "000000000000000000000000000000000000000000000000000000000000000001d3475bf7655b0fb2d852921035b2ef607f49069b97454e6795251062741771", + expectedX: "b5da00b73cd6560520e7c364086e7cd23a34bf60d0e707be9fc34d4cd5fdfa2c", + }, + { + ellswift: "000000000000000000000000000000000000000000000000000000000000000082277c4a71f9d22e66ece523f8fa08741a7c0912c66a69ce68514bfd3515b49f", + expectedX: "f482f2e241753ad0fb89150d8491dc1e34ff0b8acfbb442cfe999e2e5e6fd1d2", + }, + { + ellswift: "00000000000000000000000000000000000000000000000000000000000000008421cc930e77c9f514b6915c3dbe2a94c6d8f690b5b739864ba6789fb8a55dd0", + expectedX: "9f59c40275f5085a006f05dae77eb98c6fd0db1ab4a72ac47eae90a4fc9e57e0", + }, + { + ellswift: "0000000000000000000000000000000000000000000000000000000000000000bde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441", + expectedX: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9fffffd6b", + }, + { + ellswift: "0000000000000000000000000000000000000000000000000000000000000000d19c182d2759cd99824228d94799f8c6557c38a1c0d6779b9d4b729c6f1ccc42", + expectedX: "70720db7e238d04121f5b1afd8cc5ad9d18944c6bdc94881f502b7a3af3aecff", + }, + { + ellswift: "0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c", + }, + { + ellswift: "0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2664bbd5", + expectedX: "50873db31badcc71890e4f67753a65757f97aaa7dd5f1e82b753ace32219064b", + }, + { + ellswift: "0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7028de7d", + expectedX: "1eea9cc59cfcf2fa151ac6c274eea4110feb4f7b68c5965732e9992e976ef68e", + }, + { + ellswift: "0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffcbcfb7e7", + expectedX: "12303941aedc208880735b1f1795c8e55be520ea93e103357b5d2adb7ed59b8e", + }, + { + ellswift: "0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffff3113ad9", + expectedX: "7eed6b70e7b0767c7d7feac04e57aa2a12fef5e0f48f878fcbb88b3b6b5e0783", + }, + { + ellswift: "0a2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f8530000000000000000000000000000000000000000000000000000000000000000", + expectedX: "532167c11200b08c0e84a354e74dcc40f8b25f4fe686e30869526366278a0688", + }, + { + ellswift: "0a2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f853fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "532167c11200b08c0e84a354e74dcc40f8b25f4fe686e30869526366278a0688", + }, + { + ellswift: "0ffde9ca81d751e9cdaffc1a50779245320b28996dbaf32f822f20117c22fbd6c74d99efceaa550f1ad1c0f43f46e7ff1ee3bd0162b7bf55f2965da9c3450646", + expectedX: "74e880b3ffd18fe3cddf7902522551ddf97fa4a35a3cfda8197f947081a57b8f", + }, + { + ellswift: "0ffde9ca81d751e9cdaffc1a50779245320b28996dbaf32f822f20117c22fbd6ffffffffffffffffffffffffffffffffffffffffffffffffffffffff156ca896", + expectedX: "377b643fce2271f64e5c8101566107c1be4980745091783804f654781ac9217c", + }, + { + ellswift: "123658444f32be8f02ea2034afa7ef4bbe8adc918ceb49b12773b625f490b368ffffffffffffffffffffffffffffffffffffffffffffffffffffffff8dc5fe11", + expectedX: "ed16d65cf3a9538fcb2c139f1ecbc143ee14827120cbc2659e667256800b8142", + }, + { + ellswift: "146f92464d15d36e35382bd3ca5b0f976c95cb08acdcf2d5b3570617990839d7ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3145e93b", + expectedX: "0d5cd840427f941f65193079ab8e2e83024ef2ee7ca558d88879ffd879fb6657", + }, + { + ellswift: "15fdf5cf09c90759add2272d574d2bb5fe1429f9f3c14c65e3194bf61b82aa73ffffffffffffffffffffffffffffffffffffffffffffffffffffffff04cfd906", + expectedX: "16d0e43946aec93f62d57eb8cde68951af136cf4b307938dd1447411e07bffe1", + }, + { + ellswift: "1f67edf779a8a649d6def60035f2fa22d022dd359079a1a144073d84f19b92d50000000000000000000000000000000000000000000000000000000000000000", + expectedX: "025661f9aba9d15c3118456bbe980e3e1b8ba2e047c737a4eb48a040bb566f6c", + }, + { + ellswift: "1f67edf779a8a649d6def60035f2fa22d022dd359079a1a144073d84f19b92d5fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "025661f9aba9d15c3118456bbe980e3e1b8ba2e047c737a4eb48a040bb566f6c", + }, + { + ellswift: "1fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "98bec3b2a351fa96cfd191c1778351931b9e9ba9ad1149f6d9eadca80981b801", + }, + { + ellswift: "4056a34a210eec7892e8820675c860099f857b26aad85470ee6d3cf1304a9dcf375e70374271f20b13c9986ed7d3c17799698cfc435dbed3a9f34b38c823c2b4", + expectedX: "868aac2003b29dbcad1a3e803855e078a89d16543ac64392d122417298cec76e", + }, + { + ellswift: "4197ec3723c654cfdd32ab075506648b2ff5070362d01a4fff14b336b78f963fffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3ab1e95", + expectedX: "ba5a6314502a8952b8f456e085928105f665377a8ce27726a5b0eb7ec1ac0286", + }, + { + ellswift: "47eb3e208fedcdf8234c9421e9cd9a7ae873bfbdbc393723d1ba1e1e6a8e6b24ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7cd12cb1", + expectedX: "d192d52007e541c9807006ed0468df77fd214af0a795fe119359666fdcf08f7c", + }, + { + ellswift: "5eb9696a2336fe2c3c666b02c755db4c0cfd62825c7b589a7b7bb442e141c1d693413f0052d49e64abec6d5831d66c43612830a17df1fe4383db896468100221", + expectedX: "ef6e1da6d6c7627e80f7a7234cb08a022c1ee1cf29e4d0f9642ae924cef9eb38", + }, + { + ellswift: "7bf96b7b6da15d3476a2b195934b690a3a3de3e8ab8474856863b0de3af90b0e0000000000000000000000000000000000000000000000000000000000000000", + expectedX: "50851dfc9f418c314a437295b24feeea27af3d0cd2308348fda6e21c463e46ff", + }, + { + ellswift: "7bf96b7b6da15d3476a2b195934b690a3a3de3e8ab8474856863b0de3af90b0efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "50851dfc9f418c314a437295b24feeea27af3d0cd2308348fda6e21c463e46ff", + }, + { + ellswift: "851b1ca94549371c4f1f7187321d39bf51c6b7fb61f7cbf027c9da62021b7a65fc54c96837fb22b362eda63ec52ec83d81bedd160c11b22d965d9f4a6d64d251", + expectedX: "3e731051e12d33237eb324f2aa5b16bb868eb49a1aa1fadc19b6e8761b5a5f7b", + }, + { + ellswift: "943c2f775108b737fe65a9531e19f2fc2a197f5603e3a2881d1d83e4008f91250000000000000000000000000000000000000000000000000000000000000000", + expectedX: "311c61f0ab2f32b7b1f0223fa72f0a78752b8146e46107f8876dd9c4f92b2942", + }, + { + ellswift: "943c2f775108b737fe65a9531e19f2fc2a197f5603e3a2881d1d83e4008f9125fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "311c61f0ab2f32b7b1f0223fa72f0a78752b8146e46107f8876dd9c4f92b2942", + }, + { + ellswift: "a0f18492183e61e8063e573606591421b06bc3513631578a73a39c1c3306239f2f32904f0d2a33ecca8a5451705bb537d3bf44e071226025cdbfd249fe0f7ad6", + expectedX: "97a09cf1a2eae7c494df3c6f8a9445bfb8c09d60832f9b0b9d5eabe25fbd14b9", + }, + { + ellswift: "a1ed0a0bd79d8a23cfe4ec5fef5ba5cccfd844e4ff5cb4b0f2e71627341f1c5b17c499249e0ac08d5d11ea1c2c8ca7001616559a7994eadec9ca10fb4b8516dc", + expectedX: "65a89640744192cdac64b2d21ddf989cdac7500725b645bef8e2200ae39691f2", + }, + { + ellswift: "ba94594a432721aa3580b84c161d0d134bc354b690404d7cd4ec57c16d3fbe98ffffffffffffffffffffffffffffffffffffffffffffffffffffffffea507dd7", + expectedX: "5e0d76564aae92cb347e01a62afd389a9aa401c76c8dd227543dc9cd0efe685a", + }, + { + ellswift: "bcaf7219f2f6fbf55fe5e062dce0e48c18f68103f10b8198e974c184750e1be3932016cbf69c4471bd1f656c6a107f1973de4af7086db897277060e25677f19a", + expectedX: "2d97f96cac882dfe73dc44db6ce0f1d31d6241358dd5d74eb3d3b50003d24c2b", + }, + { + ellswift: "bcaf7219f2f6fbf55fe5e062dce0e48c18f68103f10b8198e974c184750e1be3ffffffffffffffffffffffffffffffffffffffffffffffffffffffff6507d09a", + expectedX: "e7008afe6e8cbd5055df120bd748757c686dadb41cce75e4addcc5e02ec02b44", + }, + { + ellswift: "c5981bae27fd84401c72a155e5707fbb811b2b620645d1028ea270cbe0ee225d4b62aa4dca6506c1acdbecc0552569b4b21436a5692e25d90d3bc2eb7ce24078", + expectedX: "948b40e7181713bc018ec1702d3d054d15746c59a7020730dd13ecf985a010d7", + }, + { + ellswift: "c894ce48bfec433014b931a6ad4226d7dbd8eaa7b6e3faa8d0ef94052bcf8cff336eeb3919e2b4efb746c7f71bbca7e9383230fbbc48ffafe77e8bcc69542471", + expectedX: "f1c91acdc2525330f9b53158434a4d43a1c547cff29f15506f5da4eb4fe8fa5a", + }, + { + ellswift: "cbb0deab125754f1fdb2038b0434ed9cb3fb53ab735391129994a535d925f6730000000000000000000000000000000000000000000000000000000000000000", + expectedX: "872d81ed8831d9998b67cb7105243edbf86c10edfebb786c110b02d07b2e67cd", + }, + { + ellswift: "d917b786dac35670c330c9c5ae5971dfb495c8ae523ed97ee2420117b171f41effffffffffffffffffffffffffffffffffffffffffffffffffffffff2001f6f6", + expectedX: "e45b71e110b831f2bdad8651994526e58393fde4328b1ec04d59897142584691", + }, + { + ellswift: "e28bd8f5929b467eb70e04332374ffb7e7180218ad16eaa46b7161aa679eb4260000000000000000000000000000000000000000000000000000000000000000", + expectedX: "66b8c980a75c72e598d383a35a62879f844242ad1e73ff12edaa59f4e58632b5", + }, + { + ellswift: "e28bd8f5929b467eb70e04332374ffb7e7180218ad16eaa46b7161aa679eb426fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "66b8c980a75c72e598d383a35a62879f844242ad1e73ff12edaa59f4e58632b5", + }, + { + ellswift: "e7ee5814c1706bf8a89396a9b032bc014c2cac9c121127dbf6c99278f8bb53d1dfd04dbcda8e352466b6fcd5f2dea3e17d5e133115886eda20db8a12b54de71b", + expectedX: "e842c6e3529b234270a5e97744edc34a04d7ba94e44b6d2523c9cf0195730a50", + }, + { + ellswift: "f292e46825f9225ad23dc057c1d91c4f57fcb1386f29ef10481cb1d22518593fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7011c989", + expectedX: "3cea2c53b8b0170166ac7da67194694adacc84d56389225e330134dab85a4d55", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0000000000000000000000000000000000000000000000000000000000000000", + expectedX: "edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f01d3475bf7655b0fb2d852921035b2ef607f49069b97454e6795251062741771", + expectedX: "b5da00b73cd6560520e7c364086e7cd23a34bf60d0e707be9fc34d4cd5fdfa2c", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee", + expectedX: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9fffffd6b", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f82277c4a71f9d22e66ece523f8fa08741a7c0912c66a69ce68514bfd3515b49f", + expectedX: "f482f2e241753ad0fb89150d8491dc1e34ff0b8acfbb442cfe999e2e5e6fd1d2", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8421cc930e77c9f514b6915c3dbe2a94c6d8f690b5b739864ba6789fb8a55dd0", + expectedX: "9f59c40275f5085a006f05dae77eb98c6fd0db1ab4a72ac47eae90a4fc9e57e0", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fd19c182d2759cd99824228d94799f8c6557c38a1c0d6779b9d4b729c6f1ccc42", + expectedX: "70720db7e238d04121f5b1afd8cc5ad9d18944c6bdc94881f502b7a3af3aecff", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2664bbd5", + expectedX: "50873db31badcc71890e4f67753a65757f97aaa7dd5f1e82b753ace32219064b", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7028de7d", + expectedX: "1eea9cc59cfcf2fa151ac6c274eea4110feb4f7b68c5965732e9992e976ef68e", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffffcbcfb7e7", + expectedX: "12303941aedc208880735b1f1795c8e55be520ea93e103357b5d2adb7ed59b8e", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3113ad9", + expectedX: "7eed6b70e7b0767c7d7feac04e57aa2a12fef5e0f48f878fcbb88b3b6b5e0783", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff13cea4a70000000000000000000000000000000000000000000000000000000000000000", + expectedX: "649984435b62b4a25d40c6133e8d9ab8c53d4b059ee8a154a3be0fcf4e892edb", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff13cea4a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "649984435b62b4a25d40c6133e8d9ab8c53d4b059ee8a154a3be0fcf4e892edb", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff15028c590063f64d5a7f1c14915cd61eac886ab295bebd91992504cf77edb028bdd6267f", + expectedX: "3fde5713f8282eead7d39d4201f44a7c85a5ac8a0681f35e54085c6b69543374", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2715de860000000000000000000000000000000000000000000000000000000000000000", + expectedX: "3524f77fa3a6eb4389c3cb5d27f1f91462086429cd6c0cb0df43ea8f1e7b3fb4", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2715de86fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "3524f77fa3a6eb4389c3cb5d27f1f91462086429cd6c0cb0df43ea8f1e7b3fb4", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2c2c5709e7156c417717f2feab147141ec3da19fb759575cc6e37b2ea5ac9309f26f0f66", + expectedX: "d2469ab3e04acbb21c65a1809f39caafe7a77c13d10f9dd38f391c01dc499c52", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3a08cc1efffffffffffffffffffffffffffffffffffffffffffffffffffffffff760e9f0", + expectedX: "38e2a5ce6a93e795e16d2c398bc99f0369202ce21e8f09d56777b40fc512bccc", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e91257d932016cbf69c4471bd1f656c6a107f1973de4af7086db897277060e25677f19a", + expectedX: "864b3dc902c376709c10a93ad4bbe29fce0012f3dc8672c6286bba28d7d6d6fc", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff795d6c1c322cadf599dbb86481522b3cc55f15a67932db2afa0111d9ed6981bcd124bf44", + expectedX: "766dfe4a700d9bee288b903ad58870e3d4fe2f0ef780bcac5c823f320d9a9bef", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff8e426f0392389078c12b1a89e9542f0593bc96b6bfde8224f8654ef5d5cda935a3582194", + expectedX: "faec7bc1987b63233fbc5f956edbf37d54404e7461c58ab8631bc68e451a0478", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff91192139ffffffffffffffffffffffffffffffffffffffffffffffffffffffff45f0f1eb", + expectedX: "ec29a50bae138dbf7d8e24825006bb5fc1a2cc1243ba335bc6116fb9e498ec1f", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff98eb9ab76e84499c483b3bf06214abfe065dddf43b8601de596d63b9e45a166a580541fe", + expectedX: "1e0ff2dee9b09b136292a9e910f0d6ac3e552a644bba39e64e9dd3e3bbd3d4d4", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff9b77b7f2c74d99efceaa550f1ad1c0f43f46e7ff1ee3bd0162b7bf55f2965da9c3450646", + expectedX: "8b7dd5c3edba9ee97b70eff438f22dca9849c8254a2f3345a0a572ffeaae0928", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff9b77b7f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffff156ca896", + expectedX: "0881950c8f51d6b9a6387465d5f12609ef1bb25412a08a74cb2dfb200c74bfbf", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa2f5cd838816c16c4fe8a1661d606fdb13cf9af04b979a2e159a09409ebc8645d58fde02", + expectedX: "2f083207b9fd9b550063c31cd62b8746bd543bdc5bbf10e3a35563e927f440c8", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffb13f75c00000000000000000000000000000000000000000000000000000000000000000", + expectedX: "4f51e0be078e0cddab2742156adba7e7a148e73157072fd618cd60942b146bd0", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffb13f75c0fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "4f51e0be078e0cddab2742156adba7e7a148e73157072fd618cd60942b146bd0", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7bc1f8d0000000000000000000000000000000000000000000000000000000000000000", + expectedX: "16c2ccb54352ff4bd794f6efd613c72197ab7082da5b563bdf9cb3edaafe74c2", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7bc1f8dfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expectedX: "16c2ccb54352ff4bd794f6efd613c72197ab7082da5b563bdf9cb3edaafe74c2", + }, + { + ellswift: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffef64d162750546ce42b0431361e52d4f5242d8f24f33e6b1f99b591647cbc808f462af51", + expectedX: "d41244d11ca4f65240687759f95ca9efbab767ededb38fd18c36e18cd3b6f6a9", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0e5be52372dd6e894b2a326fc3605a6e8f3c69c710bf27d630dfe2004988b78eb6eab36", + expectedX: "64bf84dd5e03670fdb24c0f5d3c2c365736f51db6c92d95010716ad2d36134c8", + }, + { + ellswift: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefbb982fffffffffffffffffffffffffffffffffffffffffffffffffffffffff6d6db1f", + expectedX: "1c92ccdfcf4ac550c28db57cff0c8515cb26936c786584a70114008d6c33a34b", + }, + } + + for _, test := range tests { + // The test.ellswift variable is 128-bytes long and is composed of two + // 64-byte hexadecimal strings. The first 64-byte hex string is the u + // value and the other is the t-value. + uVal := setHex(test.ellswift[0:64]).Normalize() + tVal := setHex(test.ellswift[64:128]).Normalize() + + xVal := setHex(test.expectedX) + + if !XSwiftEC(uVal, tVal).Equals(xVal) { + t.Fatalf("encoding not equal to x") + } + } +} + +// TestXSwiftECInvVectors tests that the inverse of the ElligatorSwift encoding +// XSwiftECInv works as expected. In other words, given a u-value and an +// x-value, that the correct t-value is returned. Each test case has a cases +// array that determines which t-value should be returned for each +// corresponding case. +func TestXSwiftECInvVectors(t *testing.T) { + tests := []struct { + u string + x string + cases []string + }{ + { + u: "05ff6bdad900fc3261bc7fe34e2fb0f569f06e091ae437d3a52e9da0cbfb9590", + x: "80cdf63774ec7022c89a5a8558e373a279170285e0ab27412dbce510bdfe23fc", + cases: []string{ + "", + "", + "45654798ece071ba79286d04f7f3eb1c3f1d17dd883610f2ad2efd82a287466b", + "0aeaa886f6b76c7158452418cbf5033adc5747e9e9b5d3b2303db96936528557", + "", + "", + "ba9ab867131f8e4586d792fb080c14e3c0e2e82277c9ef0d52d1027c5d78b5c4", + "f51557790948938ea7badbe7340afcc523a8b816164a2c4dcfc24695c9ad76d8", + }, + }, + { + u: "1737a85f4c8d146cec96e3ffdca76d9903dcf3bd53061868d478c78c63c2aa9e", + x: "39e48dd150d2f429be088dfd5b61882e7e8407483702ae9a5ab35927b15f85ea", + cases: []string{ + "1be8cc0b04be0c681d0c6a68f733f82c6c896e0c8a262fcd392918e303a7abf4", + "605b5814bf9b8cb066667c9e5480d22dc5b6c92f14b4af3ee0a9eb83b03685e3", + "", + "", + "e41733f4fb41f397e2f3959708cc07d3937691f375d9d032c6d6e71bfc58503b", + "9fa4a7eb4064734f99998361ab7f2dd23a4936d0eb4b50c11f56147b4fc9764c", + "", + "", + }, + }, + { + u: "1aaa1ccebf9c724191033df366b36f691c4d902c228033ff4516d122b2564f68", + x: "c75541259d3ba98f207eaa30c69634d187d0b6da594e719e420f4898638fc5b0", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "2323a1d079b0fd72fc8bb62ec34230a815cb0596c2bfac998bd6b84260f5dc26", + x: "239342dfb675500a34a196310b8d87d54f49dcac9da50c1743ceab41a7b249ff", + cases: []string{ + "f63580b8aa49c4846de56e39e1b3e73f171e881eba8c66f614e67e5c975dfc07", + "b6307b332e699f1cf77841d90af25365404deb7fed5edb3090db49e642a156b6", + "", + "", + "09ca7f4755b63b7b921a91c61e4c18c0e8e177e145739909eb1981a268a20028", + "49cf84ccd19660e30887be26f50dac9abfb2148012a124cf6f24b618bd5ea579", + "", + "", + }, + }, + { + u: "2dc90e640cb646ae9164c0b5a9ef0169febe34dc4437d6e46acb0e27e219d1e8", + x: "d236f19bf349b9516e9b3f4a5610fe960141cb23bbc8291b9534f1d71de62a47", + cases: []string{ + "e69df7d9c026c36600ebdf588072675847c0c431c8eb730682533e964b6252c9", + "4f18bbdf7c2d6c5f818c18802fa35cd069eaa79fff74e4fc837c80d93fece2f8", + "", + "", + "196208263fd93c99ff1420a77f8d98a7b83f3bce37148cf97dacc168b49da966", + "b0e7442083d293a07e73e77fd05ca32f96155860008b1b037c837f25c0131937", + "", + "", + }, + }, + { + u: "3edd7b3980e2f2f34d1409a207069f881fda5f96f08027ac4465b63dc278d672", + x: "053a98de4a27b1961155822b3a3121f03b2a14458bd80eb4a560c4c7a85c149c", + cases: []string{ + "", + "", + "b3dae4b7dcf858e4c6968057cef2b156465431526538199cf52dc1b2d62fda30", + "4aa77dd55d6b6d3cfa10cc9d0fe42f79232e4575661049ae36779c1d0c666d88", + "", + "", + "4c251b482307a71b39697fa8310d4ea9b9abcead9ac7e6630ad23e4c29d021ff", + "b558822aa29492c305ef3362f01bd086dcd1ba8a99efb651c98863e1f3998ea7", + }, + }, + { + u: "4295737efcb1da6fb1d96b9ca7dcd1e320024b37a736c4948b62598173069f70", + x: "fa7ffe4f25f88362831c087afe2e8a9b0713e2cac1ddca6a383205a266f14307", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "587c1a0cee91939e7f784d23b963004a3bf44f5d4e32a0081995ba20b0fca59e", + x: "2ea988530715e8d10363907ff25124524d471ba2454d5ce3be3f04194dfd3a3c", + cases: []string{ + "cfd5a094aa0b9b8891b76c6ab9438f66aa1c095a65f9f70135e8171292245e74", + "a89057d7c6563f0d6efa19ae84412b8a7b47e791a191ecdfdf2af84fd97bc339", + "475d0ae9ef46920df07b34117be5a0817de1023e3cc32689e9be145b406b0aef", + "a0759178ad80232454f827ef05ea3e72ad8d75418e6d4cc1cd4f5306c5e7c453", + "302a5f6b55f464776e48939546bc709955e3f6a59a0608feca17e8ec6ddb9dbb", + "576fa82839a9c0f29105e6517bbed47584b8186e5e6e132020d507af268438f6", + "b8a2f51610b96df20f84cbee841a5f7e821efdc1c33cd9761641eba3bf94f140", + "5f8a6e87527fdcdbab07d810fa15c18d52728abe7192b33e32b0acf83a1837dc", + }, + }, + { + u: "5fa88b3365a635cbbcee003cce9ef51dd1a310de277e441abccdb7be1e4ba249", + x: "79461ff62bfcbcac4249ba84dd040f2cec3c63f725204dc7f464c16bf0ff3170", + cases: []string{ + "", + "", + "6bb700e1f4d7e236e8d193ff4a76c1b3bcd4e2b25acac3d51c8dac653fe909a0", + "f4c73410633da7f63a4f1d55aec6dd32c4c6d89ee74075edb5515ed90da9e683", + "", + "", + "9448ff1e0b281dc9172e6c00b5893e4c432b1d4da5353c2ae3725399c016f28f", + "0b38cbef9cc25809c5b0e2aa513922cd3b39276118bf8a124aaea125f25615ac", + }, + }, + { + u: "6fb31c7531f03130b42b155b952779efbb46087dd9807d241a48eac63c3d96d6", + x: "56f81be753e8d4ae4940ea6f46f6ec9fda66a6f96cc95f506cb2b57490e94260", + cases: []string{ + "", + "", + "59059774795bdb7a837fbe1140a5fa59984f48af8df95d57dd6d1c05437dcec1", + "22a644db79376ad4e7b3a009e58b3f13137c54fdf911122cc93667c47077d784", + "", + "", + "a6fa688b86a424857c8041eebf5a05a667b0b7507206a2a82292e3f9bc822d6e", + "dd59bb2486c8952b184c5ff61a74c0ecec83ab0206eeedd336c9983a8f8824ab", + }, + }, + { + u: "704cd226e71cb6826a590e80dac90f2d2f5830f0fdf135a3eae3965bff25ff12", + x: "138e0afa68936ee670bd2b8db53aedbb7bea2a8597388b24d0518edd22ad66ec", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "725e914792cb8c8949e7e1168b7cdd8a8094c91c6ec2202ccd53a6a18771edeb", + x: "8da16eb86d347376b6181ee9748322757f6b36e3913ddfd332ac595d788e0e44", + cases: []string{ + "dd357786b9f6873330391aa5625809654e43116e82a5a5d82ffd1d6624101fc4", + "a0b7efca01814594c59c9aae8e49700186ca5d95e88bcc80399044d9c2d8613d", + "", + "", + "22ca8879460978cccfc6e55a9da7f69ab1bcee917d5a5a27d002e298dbefdc6b", + "5f481035fe7eba6b3a63655171b68ffe7935a26a1774337fc66fbb253d279af2", + "", + "", + }, + }, + { + u: "78fe6b717f2ea4a32708d79c151bf503a5312a18c0963437e865cc6ed3f6ae97", + x: "8701948e80d15b5cd8f72863eae40afc5aced5e73f69cbc8179a33902c094d98", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "7c37bb9c5061dc07413f11acd5a34006e64c5c457fdb9a438f217255a961f50d", + x: "5c1a76b44568eb59d6789a7442d9ed7cdc6226b7752b4ff8eaf8e1a95736e507", + cases: []string{ + "", + "", + "b94d30cd7dbff60b64620c17ca0fafaa40b3d1f52d077a60a2e0cafd145086c2", + "", + "", + "", + "46b2cf32824009f49b9df3e835f05055bf4c2e0ad2f8859f5d1f3501ebaf756d", + "", + }, + }, + { + u: "82388888967f82a6b444438a7d44838e13c0d478b9ca060da95a41fb94303de6", + x: "29e9654170628fec8b4972898b113cf98807f4609274f4f3140d0674157c90a0", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "91298f5770af7a27f0a47188d24c3b7bf98ab2990d84b0b898507e3c561d6472", + x: "144f4ccbd9a74698a88cbf6fd00ad886d339d29ea19448f2c572cac0a07d5562", + cases: []string{ + "e6a0ffa3807f09dadbe71e0f4be4725f2832e76cad8dc1d943ce839375eff248", + "837b8e68d4917544764ad0903cb11f8615d2823cefbb06d89049dbabc69befda", + "", + "", + "195f005c7f80f6252418e1f0b41b8da0d7cd189352723e26bc317c6b8a1009e7", + "7c8471972b6e8abb89b52f6fc34ee079ea2d7dc31044f9276fb6245339640c55", + "", + "", + }, + }, + { + u: "b682f3d03bbb5dee4f54b5ebfba931b4f52f6a191e5c2f483c73c66e9ace97e1", + x: "904717bf0bc0cb7873fcdc38aa97f19e3a62630972acff92b24cc6dda197cb96", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "c17ec69e665f0fb0dbab48d9c2f94d12ec8a9d7eacb58084833091801eb0b80b", + x: "147756e66d96e31c426d3cc85ed0c4cfbef6341dd8b285585aa574ea0204b55e", + cases: []string{ + "6f4aea431a0043bdd03134d6d9159119ce034b88c32e50e8e36c4ee45eac7ae9", + "fd5be16d4ffa2690126c67c3ef7cb9d29b74d397c78b06b3605fda34dc9696a6", + "5e9c60792a2f000e45c6250f296f875e174efc0e9703e628706103a9dd2d82c7", + "", + "90b515bce5ffbc422fcecb2926ea6ee631fcb4773cd1af171c93b11aa1538146", + "02a41e92b005d96fed93983c1083462d648b2c683874f94c9fa025ca23696589", + "a1639f86d5d0fff1ba39daf0d69078a1e8b103f168fc19d78f9efc5522d27968", + "", + }, + }, + { + u: "c25172fc3f29b6fc4a1155b8575233155486b27464b74b8b260b499a3f53cb14", + x: "1ea9cbdb35cf6e0329aa31b0bb0a702a65123ed008655a93b7dcd5280e52e1ab", + cases: []string{ + "", + "", + "7422edc7843136af0053bb8854448a8299994f9ddcefd3a9a92d45462c59298a", + "78c7774a266f8b97ea23d05d064f033c77319f923f6b78bce4e20bf05fa5398d", + "", + "", + "8bdd12387bcec950ffac4477abbb757d6666b06223102c5656d2bab8d3a6d2a5", + "873888b5d990746815dc2fa2f9b0fcc388ce606dc09487431b1df40ea05ac2a2", + }, + }, + { + u: "cab6626f832a4b1280ba7add2fc5322ff011caededf7ff4db6735d5026dc0367", + x: "2b2bef0852c6f7c95d72ac99a23802b875029cd573b248d1f1b3fc8033788eb6", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "d8621b4ffc85b9ed56e99d8dd1dd24aedcecb14763b861a17112dc771a104fd2", + x: "812cabe972a22aa67c7da0c94d8a936296eb9949d70c37cb2b2487574cb3ce58", + cases: []string{ + "fbc5febc6fdbc9ae3eb88a93b982196e8b6275a6d5a73c17387e000c711bd0e3", + "8724c96bd4e5527f2dd195a51c468d2d211ba2fac7cbe0b4b3434253409fb42d", + "", + "", + "043a014390243651c147756c467de691749d8a592a58c3e8c781fff28ee42b4c", + "78db36942b1aad80d22e6a5ae3b972d2dee45d0538341f4b4cbcbdabbf604802", + "", + "", + }, + }, + { + u: "da463164c6f4bf7129ee5f0ec00f65a675a8adf1bd931b39b64806afdcda9a22", + x: "25b9ce9b390b408ed611a0f13ff09a598a57520e426ce4c649b7f94f2325620d", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "dafc971e4a3a7b6dcfb42a08d9692d82ad9e7838523fcbda1d4827e14481ae2d", + x: "250368e1b5c58492304bd5f72696d27d526187c7adc03425e2b7d81dbb7e4e02", + cases: []string{ + "", + "", + "370c28f1be665efacde6aa436bf86fe21e6e314c1e53dd040e6c73a46b4c8c49", + "cd8acee98ffe56531a84d7eb3e48fa4034206ce825ace907d0edf0eaeb5e9ca2", + "", + "", + "c8f3d70e4199a105321955bc9407901de191ceb3e1ac22fbf1938c5a94b36fe6", + "327531167001a9ace57b2814c1b705bfcbdf9317da5316f82f120f1414a15f8d", + }, + }, + { + u: "e0294c8bc1a36b4166ee92bfa70a5c34976fa9829405efea8f9cd54dcb29b99e", + x: "ae9690d13b8d20a0fbbf37bed8474f67a04e142f56efd78770a76b359165d8a1", + cases: []string{ + "", + "", + "dcd45d935613916af167b029058ba3a700d37150b9df34728cb05412c16d4182", + "", + "", + "", + "232ba26ca9ec6e950e984fd6fa745c58ff2c8eaf4620cb8d734fabec3e92baad", + "", + }, + }, + { + u: "e148441cd7b92b8b0e4fa3bd68712cfd0d709ad198cace611493c10e97f5394e", + x: "164a639794d74c53afc4d3294e79cdb3cd25f99f6df45c000f758aba54d699c0", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "e4b00ec97aadcca97644d3b0c8a931b14ce7bcf7bc8779546d6e35aa5937381c", + x: "94e9588d41647b3fcc772dc8d83c67ce3be003538517c834103d2cd49d62ef4d", + cases: []string{ + "c88d25f41407376bb2c03a7fffeb3ec7811cc43491a0c3aac0378cdc78357bee", + "51c02636ce00c2345ecd89adb6089fe4d5e18ac924e3145e6669501cd37a00d4", + "205b3512db40521cb200952e67b46f67e09e7839e0de44004138329ebd9138c5", + "58aab390ab6fb55c1d1b80897a207ce94a78fa5b4aa61a33398bcae9adb20d3e", + "3772da0bebf8c8944d3fc5800014c1387ee33bcb6e5f3c553fc8732287ca8041", + "ae3fd9c931ff3dcba132765249f7601b2a1e7536db1ceba19996afe22c85fb5b", + "dfa4caed24bfade34dff6ad1984b90981f6187c61f21bbffbec7cd60426ec36a", + "a7554c6f54904aa3e2e47f7685df8316b58705a4b559e5ccc6743515524deef1", + }, + }, + { + u: "e5bbb9ef360d0a501618f0067d36dceb75f5be9a620232aa9fd5139d0863fde5", + x: "e5bbb9ef360d0a501618f0067d36dceb75f5be9a620232aa9fd5139d0863fde5", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + { + u: "e6bcb5c3d63467d490bfa54fbbc6092a7248c25e11b248dc2964a6e15edb1457", + x: "19434a3c29cb982b6f405ab04439f6d58db73da1ee4db723d69b591da124e7d8", + cases: []string{ + "67119877832ab8f459a821656d8261f544a553b89ae4f25c52a97134b70f3426", + "ffee02f5e649c07f0560eff1867ec7b32d0e595e9b1c0ea6e2a4fc70c97cd71f", + "b5e0c189eb5b4bacd025b7444d74178be8d5246cfa4a9a207964a057ee969992", + "5746e4591bf7f4c3044609ea372e908603975d279fdef8349f0b08d32f07619d", + "98ee67887cd5470ba657de9a927d9e0abb5aac47651b0da3ad568eca48f0c809", + "0011fd0a19b63f80fa9f100e7981384cd2f1a6a164e3f1591d5b038e36832510", + "4a1f3e7614a4b4532fda48bbb28be874172adb9305b565df869b5fa71169629d", + "a8b91ba6e4080b3cfbb9f615c8d16f79fc68a2d8602107cb60f4f72bd0f89a92", + }, + }, + { + u: "f28fba64af766845eb2f4302456e2b9f8d80affe57e7aae42738d7cddb1c2ce6", + x: "f28fba64af766845eb2f4302456e2b9f8d80affe57e7aae42738d7cddb1c2ce6", + cases: []string{ + "4f867ad8bb3d840409d26b67307e62100153273f72fa4b7484becfa14ebe7408", + "5bbc4f59e452cc5f22a99144b10ce8989a89a995ec3cea1c91ae10e8f721bb5d", + "", + "", + "b079852744c27bfbf62d9498cf819deffeacd8c08d05b48b7b41305db1418827", + "a443b0a61bad33a0dd566ebb4ef317676576566a13c315e36e51ef1608de40d2", + "", + "", + }, + }, + { + u: "f455605bc85bf48e3a908c31023faf98381504c6c6d3aeb9ede55f8dd528924d", + x: "d31fbcd5cdb798f6c00db6692f8fe8967fa9c79dd10958f4a194f01374905e99", + cases: []string{ + "", + "", + "0c00c5715b56fe632d814ad8a77f8e66628ea47a6116834f8c1218f3a03cbd50", + "df88e44fac84fa52df4d59f48819f18f6a8cd4151d162afaf773166f57c7ff46", + "", + "", + "f3ff3a8ea4a9019cd27eb527588071999d715b859ee97cb073ede70b5fc33edf", + "20771bb0537b05ad20b2a60b77e60e7095732beae2e9d505088ce98fa837fce9", + }, + }, + { + u: "f58cd4d9830bad322699035e8246007d4be27e19b6f53621317b4f309b3daa9d", + x: "78ec2b3dc0948de560148bbc7c6dc9633ad5df70a5a5750cbed721804f082a3b", + cases: []string{ + "6c4c580b76c7594043569f9dae16dc2801c16a1fbe12860881b75f8ef929bce5", + "94231355e7385c5f25ca436aa64191471aea4393d6e86ab7a35fe2afacaefd0d", + "dff2a1951ada6db574df834048149da3397a75b829abf58c7e69db1b41ac0989", + "a52b66d3c907035548028bf804711bf422aba95f1a666fc86f4648e05f29caae", + "93b3a7f48938a6bfbca9606251e923d7fe3e95e041ed79f77e48a07006d63f4a", + "6bdcecaa18c7a3a0da35bc9559be6eb8e515bc6c291795485ca01d4f5350ff22", + "200d5e6ae525924a8b207cbfb7eb625cc6858a47d6540a73819624e3be53f2a6", + "5ad4992c36f8fcaab7fd7407fb8ee40bdd5456a0e599903790b9b71ea0d63181", + }, + }, + { + u: "fd7d912a40f182a3588800d69ebfb5048766da206fd7ebc8d2436c81cbef6421", + x: "8d37c862054debe731694536ff46b273ec122b35a9bf1445ac3c4ff9f262c952", + cases: []string{ + "", + "", + "", + "", + "", + "", + "", + "", + }, + }, + } + + for _, test := range tests { + uVal := setHex(test.u).Normalize() + xVal := setHex(test.x).Normalize() + + // Loop through each individual case in the list of cases and ensure + // that the correct t-value is calculated. + for caseNum, expTString := range test.cases { + tVal := XSwiftECInv(uVal, xVal, caseNum) + + if tVal == nil { + if expTString != "" { + t.Fatalf("t value different than expected") + } + + continue + } + + expectedT := setHex(expTString) + + if !tVal.Equals(expectedT) { + t.Fatalf("t value different than expected") + } + } + } +} From 9460f4ca1269775695ed11a85bb755ad0d6e5a0f Mon Sep 17 00:00:00 2001 From: Eugene Siegel Date: Fri, 19 Jul 2024 11:06:13 -0400 Subject: [PATCH 2/4] v2transport: introduce FSChaCha20, FSChaCha20Poly1305 handshake This package introduces FSChaCha20 and FSChaCha20Poly1305 primitives that are behind the v2 encrypted handshake. Handshake code is also introduced which allows a caller to initiate a v2 handshake. --- v2transport/chacha.go | 173 +++++++++++ v2transport/go.mod | 17 + v2transport/go.sum | 19 ++ v2transport/transport.go | 570 ++++++++++++++++++++++++++++++++++ v2transport/transport_test.go | 465 +++++++++++++++++++++++++++ 5 files changed, 1244 insertions(+) create mode 100644 v2transport/chacha.go create mode 100644 v2transport/go.mod create mode 100644 v2transport/go.sum create mode 100644 v2transport/transport.go create mode 100644 v2transport/transport_test.go diff --git a/v2transport/chacha.go b/v2transport/chacha.go new file mode 100644 index 0000000000..98d59fb703 --- /dev/null +++ b/v2transport/chacha.go @@ -0,0 +1,173 @@ +package v2transport + +import ( + "crypto/cipher" + "encoding/binary" + "fmt" + + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/chacha20poly1305" +) + +const ( + // rekeyInterval is the number of messages that can be encrypted or + // decrypted with a single key before we rotate keys. + rekeyInterval = 224 + + // keySize is the size of the keys used. + keySize = 32 +) + +// FSChaCha20Poly1305 is a wrapper around ChaCha20Poly1305 that changes its +// nonce after every message and is rekeyed after every rekeying interval. +type FSChaCha20Poly1305 struct { + key []byte + packetCtr uint64 + cipher cipher.AEAD +} + +// NewFSChaCha20Poly1305 creates a new instance of FSChaCha20Poly1305. +func NewFSChaCha20Poly1305(initialKey []byte) (*FSChaCha20Poly1305, error) { + cipher, err := chacha20poly1305.New(initialKey) + if err != nil { + return nil, err + } + + f := &FSChaCha20Poly1305{ + key: initialKey, + packetCtr: 0, + cipher: cipher, + } + + return f, nil +} + +// Encrypt encrypts the plaintext using the associated data, returning the +// ciphertext or an error. +func (f *FSChaCha20Poly1305) Encrypt(aad, plaintext []byte) ([]byte, error) { + return f.crypt(aad, plaintext, false) +} + +// Decrypt decrypts the ciphertext using the assosicated data, returning the +// plaintext or an error. +func (f *FSChaCha20Poly1305) Decrypt(aad, ciphertext []byte) ([]byte, error) { + return f.crypt(aad, ciphertext, true) +} + +// crypt takes the aad and plaintext/ciphertext and either encrypts or decrypts +// `text` and returns the result. If a failure was encountered, an error will +// be returned. +func (f *FSChaCha20Poly1305) crypt(aad, text []byte, + decrypt bool) ([]byte, error) { + + // The nonce is constructed as the 4-byte little-endian encoding of the + // number of messages crypted with the current key followed by the 8-byte + // little-endian encoding of the number of re-keying performed. + var nonce [12]byte + numMsgs := uint32(f.packetCtr % rekeyInterval) + numRekeys := uint64(f.packetCtr / rekeyInterval) + binary.LittleEndian.PutUint32(nonce[0:4], numMsgs) + binary.LittleEndian.PutUint64(nonce[4:12], numRekeys) + + var result []byte + if decrypt { + // Decrypt using the nonce, ciphertext, and aad. + var err error + result, err = f.cipher.Open(nil, nonce[:], text, aad) + if err != nil { + // It is ok to error here without incrementing packetCtr because + // we will no longer be decrypting any more messages. + return nil, err + } + } else { + // Encrypt using the nonce, plaintext, and aad. + result = f.cipher.Seal(nil, nonce[:], text, aad) + } + + f.packetCtr++ + + // Rekey if we are at the rekeying interval. + if f.packetCtr%rekeyInterval == 0 { + var rekeyNonce [12]byte + rekeyNonce[0] = 0xff + rekeyNonce[1] = 0xff + rekeyNonce[2] = 0xff + rekeyNonce[3] = 0xff + copy(rekeyNonce[4:], nonce[4:]) + var dummyPlaintext [32]byte + f.key = f.cipher.Seal(nil, rekeyNonce[:], dummyPlaintext[:], nil)[:keySize] + cipher, err := chacha20poly1305.New(f.key) + if err != nil { + fmt.Println(err) + return nil, err + } + f.cipher = cipher + } + + return result, nil +} + +// FSChaCha20 ... +type FSChaCha20 struct { + key []byte + chunkCtr uint64 + cipher *chacha20.Cipher +} + +// NewFSChaCha20 ... +func NewFSChaCha20(initialKey []byte) (*FSChaCha20, error) { + var initialNonce [12]byte + numRekeys := 0 + binary.LittleEndian.PutUint64(initialNonce[4:12], uint64(numRekeys)) + + cipher, err := chacha20.NewUnauthenticatedCipher( + initialKey, initialNonce[:], + ) + if err != nil { + return nil, err + } + + return &FSChaCha20{ + key: initialKey, + chunkCtr: 0, + cipher: cipher, + }, nil +} + +// Crypt ... +func (f *FSChaCha20) Crypt(text []byte) ([]byte, error) { + // XOR the text with the keystream to get either the cipher or plaintext. + textLen := len(text) + dst := make([]byte, textLen) + f.cipher.XORKeyStream(dst, text) + + // Increment the chunkCtr every time this function is called. + f.chunkCtr++ + + // Check if we need to rekey. + if f.chunkCtr%rekeyInterval == 0 { + // Get the new key by getting 32 bytes from the keystream. Use all 0's + // so that we can get the actual bytes from XORKeyStream since the + // chacha20 library doesn't supply us with the keystream's bytes + // directly. + var dummyXor [32]byte + var newKey [32]byte + f.cipher.XORKeyStream(newKey[:], dummyXor[:]) + f.key = newKey[:] + + var nonce [12]byte + numRekeys := f.chunkCtr / rekeyInterval + binary.LittleEndian.PutUint64(nonce[4:12], numRekeys) + + cipher, err := chacha20.NewUnauthenticatedCipher( + f.key, nonce[:], + ) + if err != nil { + return nil, err + } + + f.cipher = cipher + } + + return dst, nil +} diff --git a/v2transport/go.mod b/v2transport/go.mod new file mode 100644 index 0000000000..e81a53a8b1 --- /dev/null +++ b/v2transport/go.mod @@ -0,0 +1,17 @@ +module v2transport + +go 1.23.2 + +require ( + github.com/btcsuite/btcd v0.24.2 + github.com/btcsuite/btcd/btcec/v2 v2.3.4 + golang.org/x/crypto v0.25.0 +) + +require ( + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + golang.org/x/sys v0.22.0 // indirect +) + +replace github.com/btcsuite/btcd/btcec/v2 => ./../btcec diff --git a/v2transport/go.sum b/v2transport/go.sum new file mode 100644 index 0000000000..22a0c356f8 --- /dev/null +++ b/v2transport/go.sum @@ -0,0 +1,19 @@ +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v2transport/transport.go b/v2transport/transport.go new file mode 100644 index 0000000000..5d237d7684 --- /dev/null +++ b/v2transport/transport.go @@ -0,0 +1,570 @@ +package v2transport + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "fmt" + "io" + + "golang.org/x/crypto/hkdf" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/wire" +) + +const ( + // garbageSize is the length in bytes of the garbage terminator that each + // party sends. + garbageSize = 16 + + // maxContentLen is the maximum length of content that can be encrypted. + maxContentLength = 1<<24 - 1 + + // ignoreBitPos is the position of the ignore bit in the packet's header. + ignoreBitPos = 7 + + // lengthFieldLen is the length of the length field when encrypting the + // content's length. + lengthFieldLen = 3 + + // headerLen is the length of the header field. It is composed of a single + // byte with only the ignoreBitPos having any meaning. + headerLen = 1 + + // chachapoly1305Expansion is the difference in bytes between the plaintext + // and ciphertext when using chachapoly1305. The ciphertext is larger + // because of the authentication tag. + chachapoly1305Expansion = 16 +) + +var ( + // transportVersion is the transport version we are currently using. + transportVersion = []byte{} + + // errInsufficientBytes is returned when we haven't received enough bytes + // to populate their ElligatorSwift encoded public key. + errInsufficientBytes = fmt.Errorf("insufficient bytes received") + + // ErrUseV1Protocol is returned when the initiating peer is attempting to + // use the V1 protocol. + ErrUseV1Protocol = fmt.Errorf("use v1 protocol instead") + + // errWrongNetV1Peer is returned when a v1 peer is using the wrong network. + errWrongNetV1Peer = fmt.Errorf("peer is v1 and using the wrong network") + + // errGarbageTermNotRecv is returned when a v2 peer never sends us their + // garbage terminator. + errGarbageTermNotRecv = fmt.Errorf("no garbage term received") + + // errContentLengthExceeded is returned when trying to encrypt more than + // the maximum content length. + errContentLengthExceeded = fmt.Errorf("maximum content length exceeded") + + // errFailedToRecv is returned when a Read call fails. + errFailedToRecv = fmt.Errorf("failed to recv data") +) + +// Peer defines the components necessary for sending/receiving data over the v2 +// transport. +type Peer struct { + // privkeyOurs is our private key + privkeyOurs *btcec.PrivateKey + + // ellswiftOurs is our ElligatorSwift-encoded public key. + ellswiftOurs [64]byte + + // sentGarbage is the garbage sent after the public key. This may be up to + // 4095 bytes. + sentGarbage []byte + + // receivedPrefix is used to determine which transport protocol we're + // using. + receivedPrefix []byte + + // sendL is the cipher used to send encrypted packet lengths. + sendL *FSChaCha20 + + // sendP is the cipher used to send encrypted packets. + sendP *FSChaCha20Poly1305 + + // sendGarbageTerm is the garbage terminator that we send. + sendGarbageTerm []byte + + // recvL is the cipher used to receive encrypted packet lengths. + recvL *FSChaCha20 + + // recvP is the cipher used to receive encrypted packets. + recvP *FSChaCha20Poly1305 + + // recvGarbageTerm is the garbage terminator our peer sends. + recvGarbageTerm []byte + + // initiatorL is the key used to seed the sendL cipher. + initiatorL []byte + + // initiatorP is the key used to seed the sendP cipher. + initiatorP []byte + + // responderL is the key used to seed the recvL cipher. + responderL []byte + + // responderP is the key used to seed the recvP cipher. + responderP []byte + + // sessionID uniquely identifies this encrypted channel. It is currently + // only used in the test vectors. + sessionID []byte + + // rw is the underlying object that will be read from / written to in calls + // to V2EncPacket and V2ReceivePacket. + rw io.ReadWriter +} + +// NewPeer returns a new instance of Peer. +func NewPeer() *Peer { + // The keys (initiatorL, initiatorP, responderL, responderP) as well as the + // sessionID must have space for the hkdf Expand-derived Reader to work. + return &Peer{ + receivedPrefix: make([]byte, 0), + initiatorL: make([]byte, 32), + initiatorP: make([]byte, 32), + responderL: make([]byte, 32), + responderP: make([]byte, 32), + sessionID: make([]byte, 32), + } +} + +// createV2Ciphers constructs the packet-length and packet encryption ciphers. +func (p *Peer) createV2Ciphers(ecdh_secret []byte, initiating bool, + net wire.BitcoinNet) error { + + // Define the salt as the string "bitcoin_v2_shared_secret" followed by + // the BitcoinNet's "magic" bytes. + salt := []byte("bitcoin_v2_shared_secret") + + var magic [4]byte + binary.LittleEndian.PutUint32(magic[:], uint32(net)) + salt = append(salt, magic[:]...) + + // Use the hkdf Extract function to generate a pseudo-random key. + prk := hkdf.Extract(sha256.New, ecdh_secret, salt) + + // Use the hkdf Expand function with info set to "session_id" to generate a + // unique sessionID. + sessionInfo := []byte("session_id") + sessionReader := hkdf.Expand(sha256.New, prk, sessionInfo) + _, err := sessionReader.Read(p.sessionID) + if err != nil { + return err + } + + // Use the Expand operation to generate packet and packet-length encryption + // ciphers. + initiatorLInfo := []byte("initiator_L") + initiatorLReader := hkdf.Expand(sha256.New, prk, initiatorLInfo) + _, err = initiatorLReader.Read(p.initiatorL) + if err != nil { + return err + } + + initiatorPInfo := []byte("initiator_P") + initiatorPReader := hkdf.Expand(sha256.New, prk, initiatorPInfo) + _, err = initiatorPReader.Read(p.initiatorP) + if err != nil { + return err + } + + responderLInfo := []byte("responder_L") + responderLReader := hkdf.Expand(sha256.New, prk, responderLInfo) + _, err = responderLReader.Read(p.responderL) + if err != nil { + return err + } + + responderPInfo := []byte("responder_P") + responderPReader := hkdf.Expand(sha256.New, prk, responderPInfo) + _, err = responderPReader.Read(p.responderP) + if err != nil { + return err + } + + // Create the garbage terminators that each side will use. + garbageInfo := []byte("garbage_terminators") + garbageReader := hkdf.Expand(sha256.New, prk, garbageInfo) + garbageTerminators := make([]byte, 32) + _, err = garbageReader.Read(garbageTerminators) + if err != nil { + return err + } + + initiatorGarbageTerminator := garbageTerminators[:garbageSize] + responderGarbageTerminator := garbageTerminators[garbageSize:] + + if initiating { + p.sendL, err = NewFSChaCha20(p.initiatorL) + if err != nil { + return err + } + + p.sendP, err = NewFSChaCha20Poly1305(p.initiatorP) + if err != nil { + return err + } + + p.sendGarbageTerm = initiatorGarbageTerminator + + p.recvL, err = NewFSChaCha20(p.responderL) + if err != nil { + return err + } + + p.recvP, err = NewFSChaCha20Poly1305(p.responderP) + if err != nil { + return err + } + + p.recvGarbageTerm = responderGarbageTerminator + } else { + p.sendL, err = NewFSChaCha20(p.responderL) + if err != nil { + return err + } + + p.sendP, err = NewFSChaCha20Poly1305(p.responderP) + if err != nil { + return err + } + + p.sendGarbageTerm = responderGarbageTerminator + + p.recvL, err = NewFSChaCha20(p.initiatorL) + if err != nil { + return err + } + + p.recvP, err = NewFSChaCha20Poly1305(p.initiatorP) + if err != nil { + return err + } + + p.recvGarbageTerm = initiatorGarbageTerminator + } + + // TODO: + // To achieve forward secrecy we must wipe the key material used to initialize the ciphers: + // memory_cleanse(ecdh_secret, prk, initiator_L, initiator_P, responder_L, responder_K) + // - golang analogue? + + return nil +} + +// initiateV2Handshake generates our private key and sends our public key as +// well as garbage data to our peer. +func (p *Peer) InitiateV2Handshake(garbageLen int) error { + var err error + p.privkeyOurs, p.ellswiftOurs, err = btcec.EllswiftCreate() + if err != nil { + return err + } + + p.sentGarbage = make([]byte, garbageLen) + _, err = rand.Read(p.sentGarbage) + if err != nil { + return err + } + + var data []byte + data = append(data, p.ellswiftOurs[:]...) + data = append(data, p.sentGarbage...) + + p.Send(data) + + return nil +} + +// RespondV2Handshake responds to the initiator, determines if the initiator +// wants to use the v2 protocol and if so returns our ElligatorSwift-encoded +// public key followed by our garbage data over. If the initiator does not want +// to use the v2 protocol, we'll instead revert to the v1 protocol. +func (p *Peer) RespondV2Handshake(garbageLen int, net wire.BitcoinNet) error { + var v1Prefix []byte + + // The v1 transport protocol uses the network's 4 magic bytes followed by + // "version" followed by 5 bytes of 0. + var magic [4]byte + binary.LittleEndian.PutUint32(magic[:], uint32(net)) + + versionBytes := []byte("version\x00\x00\x00\x00\x00") + + v1Prefix = append(v1Prefix, magic[:]...) + v1Prefix = append(v1Prefix, versionBytes...) + + var err error + + // Check and see if the received bytes match the v1 protocol's message + // prefix. If it does, we'll revert to the v1 protocol. If it doesn't, + // we'll treat this as a v2 peer. + for len(p.receivedPrefix) < len(v1Prefix) { + var receiveBytes []byte + receiveBytes, err = p.Receive(1) + if err != nil { + return err + } + + p.receivedPrefix = append(p.receivedPrefix, receiveBytes...) + lastIdx := len(p.receivedPrefix) - 1 + if p.receivedPrefix[lastIdx] != v1Prefix[lastIdx] { + p.privkeyOurs, p.ellswiftOurs, err = btcec.EllswiftCreate() + if err != nil { + return err + } + + p.sentGarbage = make([]byte, garbageLen) + _, err = rand.Read(p.sentGarbage) + if err != nil { + return err + } + + // Send over our ElligatorSwift-encoded pubkey followed by our + // randomly generated garbage. + var data []byte + data = append(data, p.ellswiftOurs[:]...) + data = append(data, p.sentGarbage...) + + p.Send(data) + + return nil + } + } + + return ErrUseV1Protocol +} + +// CompleteHandshake finishes the v2 protocol negotiation and optionally sends +// decoy packets after sending the garbage terminator. +func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int, + net wire.BitcoinNet) error { + + var receivedPrefix []byte + if initiating { + receivedPrefix = make([]byte, 0) + } else { + receivedPrefix = p.receivedPrefix + } + + recvData, err := p.Receive(64 - len(receivedPrefix)) + if err != nil { + return err + } + + var ellswiftTheirs [64]byte + copy(ellswiftTheirs[:], recvData) + + // Calculate the v1 protocol's message prefix and see if the bytes read + // read into ellswiftTheirs matches it. + var v1Prefix []byte + + var magic [4]byte + binary.LittleEndian.PutUint32(magic[:], uint32(net)) + + versionBytes := []byte("version\x00\x00\x00\x00\x00") + + v1Prefix = append(v1Prefix, magic[:]...) + v1Prefix = append(v1Prefix, versionBytes...) + + // ellswiftTheirs should be at least 16 bytes if receive succeeded, but + // just in case, check the size. + if len(ellswiftTheirs) < 16 { + return errInsufficientBytes + } + + if !initiating && bytes.Equal(ellswiftTheirs[4:16], v1Prefix[4:16]) { + return errWrongNetV1Peer + } + + // Calculate the shared secret to be used in creating the packet ciphers. + ecdhSecret, err := btcec.V2Ecdh( + p.privkeyOurs, ellswiftTheirs, p.ellswiftOurs, initiating, + ) + if err != nil { + return err + } + + err = p.createV2Ciphers(ecdhSecret[:], initiating, net) + if err != nil { + return err + } + + // Send garbage terminator. + p.Send(p.sendGarbageTerm) + + // Optionally send decoy packets after garbage terminator. + aad := p.sentGarbage + for i := 0; i < len(decoyContentLens); i++ { + decoyContent := make([]byte, decoyContentLens[i]) + encPacket, _, err := p.V2EncPacket(decoyContent, aad, false) + if err != nil { + return err + } + + p.Send(encPacket) + + // TODO: Does this cause anything to leak? + aad = nil + } + + // Send version packet. + _, _, err = p.V2EncPacket(transportVersion, aad, false) + if err != nil { + return err + } + + // Skip garbage until encountering garbage terminator. + recvGarbage, err := p.Receive(16) + if err != nil { + return err + } + for i := 0; i < 4096; i++ { + recvGarbageLen := len(recvGarbage) + if bytes.Equal(recvGarbage[recvGarbageLen-16:], p.recvGarbageTerm) { + _, err = p.V2ReceivePacket(recvGarbage[:recvGarbageLen-16]) + return err + } + + recvData, err := p.Receive(1) + if err != nil { + return err + } + recvGarbage = append(recvGarbage, recvData...) + } + + // Return an error if the garbage terminator was not received after 4 KiB. + return errGarbageTermNotRecv +} + +// V2EncPacket takes the contents and aad and returns a ciphertext. +func (p *Peer) V2EncPacket(contents []byte, aad []byte, ignore bool) ([]byte, + int, error) { + + contentLen := len(contents) + + if contentLen > maxContentLength { + return nil, 0, errContentLengthExceeded + } + + // Construct the packet's header based on whether or not the peer should + // ignore this packet (i.e. if this is a decoy packet). + ignoreNum := 0 + if ignore { + ignoreNum = 1 + } + + ignoreNum <<= ignoreBitPos + + header := []byte{byte(ignoreNum)} + + plaintext := make([]byte, 0) + plaintext = append(plaintext, header...) + plaintext = append(plaintext, contents...) + + aeadCiphertext, err := p.sendP.Encrypt(aad, plaintext) + if err != nil { + return nil, 0, err + } + + // We cut off a byte when feeding to Crypt. + contentsLE := make([]byte, 4) + binary.LittleEndian.PutUint32(contentsLE, uint32(contentLen)) + encContentsLen, err := p.sendL.Crypt(contentsLE[:lengthFieldLen]) + if err != nil { + return nil, 0, err + } + + encPacket := make([]byte, 0) + encPacket = append(encPacket, encContentsLen...) + encPacket = append(encPacket, aeadCiphertext...) + + totalBytes, err := p.Send(encPacket) + + return encPacket, totalBytes, err +} + +// V2ReceivePacket takes the aad and decrypts a received packet. +func (p *Peer) V2ReceivePacket(aad []byte) ([]byte, error) { + for { + // Decrypt the length field so we know how many more bytes to receive. + encContentsLen, err := p.Receive(lengthFieldLen) + if err != nil { + return nil, err + } + + contentsLenBytes, err := p.recvL.Crypt(encContentsLen) + if err != nil { + return nil, err + } + + var contentsLenLE [4]byte + copy(contentsLenLE[:], contentsLenBytes) + + contentsLen := binary.LittleEndian.Uint32(contentsLenLE[:]) + + // Decrypt the remainder of the packet. + numBytes := headerLen + int(contentsLen) + chachapoly1305Expansion + aeadCiphertext, err := p.Receive(numBytes) + if err != nil { + return nil, err + } + + plaintext, err := p.recvP.Decrypt(aad, aeadCiphertext) + if err != nil { + return nil, err + } + + // Only the first packet is expected to have non-empty AAD. If the + // ignore bit is set, ignore the packet. + // TODO: will this cause anything to leak? + aad = nil + header := plaintext[:headerLen] + if (header[0] & (1 << ignoreBitPos)) == 0 { + return plaintext[headerLen:], nil + } + } +} + +// UseWriterReader uses the passed-in ReadWriter to Send/Receive to/from. +func (p *Peer) UseReadWriter(rw io.ReadWriter) { + p.rw = rw +} + +// Send sends data to the underlying connection. It returns the number of bytes +// sent or an error. +func (p *Peer) Send(data []byte) (int, error) { + return p.rw.Write(data) +} + +// Receive receives numBytes bytes from the underlying connection. +func (p *Peer) Receive(numBytes int) ([]byte, error) { + b := make([]byte, numBytes) + index := 0 + total := 0 + for { + // TODO: Use something that inherently prevents going over? + if total > numBytes { + return nil, errFailedToRecv + } + + if total == numBytes { + return b, nil + } + + n, err := p.rw.Read(b[index:]) + if err != nil { + return nil, err + } + + total += n + index += n + } +} diff --git a/v2transport/transport_test.go b/v2transport/transport_test.go new file mode 100644 index 0000000000..3f0b318008 --- /dev/null +++ b/v2transport/transport_test.go @@ -0,0 +1,465 @@ +package v2transport + +import ( + "bytes" + "encoding/hex" + "strings" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/wire" +) + +func setHex(hexString string) *btcec.FieldVal { + if len(hexString)%2 != 0 { + hexString = "0" + hexString + } + bytes, _ := hex.DecodeString(hexString) + + var f btcec.FieldVal + f.SetByteSlice(bytes) + + return &f +} + +func TestPacketEncodingVectors(t *testing.T) { + tests := []struct { + inIdx int + inPrivOurs string + inEllswiftOurs string + inEllswiftTheirs string + inInitiating bool + inContents string + inMultiply int + inAad string + inIgnore bool + midXOurs string + midXTheirs string + midXShared string + midSharedSecret string + midInitiatorL string + midInitiatorP string + midResponderL string + midResponderP string + midSendGarbageTerm string + midRecvGarbageTerm string + outSessionID string + outCiphertext string + outCiphertextEndsWith string + }{ + { + inIdx: 1, + inPrivOurs: "61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7", + inEllswiftOurs: "ec0adff257bbfe500c188c80b4fdd640f6b45a482bbc15fc7cef5931deff0aa186f6eb9bba7b85dc4dcc28b28722de1e3d9108b985e2967045668f66098e475b", + inEllswiftTheirs: "a4a94dfce69b4a2a0a099313d10f9f7e7d649d60501c9e1d274c300e0d89aafaffffffffffffffffffffffffffffffffffffffffffffffffffffffff8faf88d5", + inInitiating: true, + inContents: "8e", + inMultiply: 1, + inAad: "", + inIgnore: false, + midXOurs: "19e965bc20fc40614e33f2f82d4eeff81b5e7516b12a5c6c0d6053527eba0923", + midXTheirs: "0c71defa3fafd74cb835102acd81490963f6b72d889495e06561375bd65f6ffc", + midXShared: "4eb2bf85bd00939468ea2abb25b63bc642e3d1eb8b967fb90caa2d89e716050e", + midSharedSecret: "c6992a117f5edbea70c3f511d32d26b9798be4b81a62eaee1a5acaa8459a3592", + midInitiatorL: "9a6478b5fbab1f4dd2f78994b774c03211c78312786e602da75a0d1767fb55cf", + midInitiatorP: "7d0c7820ba6a4d29ce40baf2caa6035e04f1e1cefd59f3e7e59e9e5af84f1f51", + midResponderL: "17bc726421e4054ac6a1d54915085aaa766f4d3cf67bbd168e6080eac289d15e", + midResponderP: "9f0fc1c0e85fd9a8eee07e6fc41dba2ff54c7729068a239ac97c37c524cca1c0", + midSendGarbageTerm: "faef555dfcdb936425d84aba524758f3", + midRecvGarbageTerm: "02cb8ff24307a6e27de3b4e7ea3fa65b", + outSessionID: "ce72dffb015da62b0d0f5474cab8bc72605225b0cee3f62312ec680ec5f41ba5", + outCiphertext: "7530d2a18720162ac09c25329a60d75adf36eda3c3", + outCiphertextEndsWith: "", + }, + { + inIdx: 999, + inPrivOurs: "1f9c581b35231838f0f17cf0c979835baccb7f3abbbb96ffcc318ab71e6e126f", + inEllswiftOurs: "a1855e10e94e00baa23041d916e259f7044e491da6171269694763f018c7e63693d29575dcb464ac816baa1be353ba12e3876cba7628bd0bd8e755e721eb0140", + inEllswiftTheirs: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0000000000000000000000000000000000000000000000000000000000000000", + inInitiating: false, + inContents: "3eb1d4e98035cfd8eeb29bac969ed3824a", + inMultiply: 1, + inAad: "", + inIgnore: false, + midXOurs: "45b6f1f684fd9f2b16e2651ddc47156c0695c8c5cd2c0c9df6d79a1056c61120", + midXTheirs: "edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c", + midXShared: "c40eb6190caf399c9007254ad5e5fa20d64af2b41696599c59b2191d16992955", + midSharedSecret: "a0138f564f74d0ad70bc337dacc9d0bf1d2349364caf1188a1e6e8ddb3b7b184", + midInitiatorL: "b82a0a7ce7219777f914d2ab873c5c487c56bd7b68622594d67fe029a8fa7def", + midInitiatorP: "d760ba8f62dd3d29d7d5584e310caf2540285edc6b51c640f9497e99c3536fd2", + midResponderL: "9db0c6f9a903cbab5d7b3c58273a3421eec0001814ec53236bd405131a0d8e90", + midResponderP: "23d2b5e653e6a3a8db160a2ca03d11cb5a79983babba861fcb57c38413323c0c", + midSendGarbageTerm: "efb64fd80acd3825ac9bc2a67216535a", + midRecvGarbageTerm: "b3cb553453bceb002897e751ff7588bf", + outSessionID: "9267c54560607de73f18c563b76a2442718879c52dd39852885d4a3c9912c9ea", + outCiphertext: "1da1bcf589f9b61872f45b7fa5371dd3f8bdf5d515b0c5f9fe9f0044afb8dc0aa1cd39a8c4", + outCiphertextEndsWith: "", + }, + { + inIdx: 0, + inPrivOurs: "0286c41cd30913db0fdff7a64ebda5c8e3e7cef10f2aebc00a7650443cf4c60d", + inEllswiftOurs: "d1ee8a93a01130cbf299249a258f94feb5f469e7d0f2f28f69ee5e9aa8f9b54a60f2c3ff2d023634ec7f4127a96cc11662e402894cf1f694fb9a7eaa5f1d9244", + inEllswiftTheirs: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff22d5e441524d571a52b3def126189d3f416890a99d4da6ede2b0cde1760ce2c3f98457ae", + inInitiating: true, + inContents: "054290a6c6ba8d80478172e89d32bf690913ae9835de6dcf206ff1f4d652286fe0ddf74deba41d55de3edc77c42a32af79bbea2c00bae7492264c60866ae5a", + inMultiply: 1, + inAad: "84932a55aac22b51e7b128d31d9f0550da28e6a3f394224707d878603386b2f9d0c6bcd8046679bfed7b68c517e7431e75d9dd34605727d2ef1c2babbf680ecc8d68d2c4886e9953a4034abde6da4189cd47c6bb3192242cf714d502ca6103ee84e08bc2ca4fd370d5ad4e7d06c7fbf496c6c7cc7eb19c40c61fb33df2a9ba48497a96c98d7b10c1f91098a6b7b16b4bab9687f27585ade1491ae0dba6a79e1e2d85dd9d9d45c5135ca5fca3f0f99a60ea39edbc9efc7923111c937913f225d67788d5f7e8852b697e26b92ec7bfcaa334a1665511c2b4c0a42d06f7ab98a9719516c8fd17f73804555ee84ab3b7d1762f6096b778d3cb9c799cbd49a9e4a325197b4e6cc4a5c4651f8b41ff88a92ec428354531f970263b467c77ed11312e2617d0d53fe9a8707f51f9f57a77bfb49afe3d89d85ec05ee17b9186f360c94ab8bb2926b65ca99dae1d6ee1af96cad09de70b6767e949023e4b380e66669914a741ed0fa420a48dbc7bfae5ef2019af36d1022283dd90655f25eec7151d471265d22a6d3f91dc700ba749bb67c0fe4bc0888593fbaf59d3c6fff1bf756a125910a63b9682b597c20f560ecb99c11a92c8c8c3f7fbfaa103146083a0ccaecf7a5f5e735a784a8820155914a289d57d8141870ffcaf588882332e0bcd8779efa931aa108dab6c3cce76691e345df4a91a03b71074d66333fd3591bff071ea099360f787bbe43b7b3dff2a59c41c7642eb79870222ad1c6f2e5a191ed5acea51134679587c9cf71c7d8ee290be6bf465c4ee47897a125708704ad610d8d00252d01959209d7cd04d5ecbbb1419a7e84037a55fefa13dee464b48a35c96bcb9a53e7ed461c3a1607ee00c3c302fd47cd73fda7493e947c9834a92d63dcfbd65aa7c38c3e3a2748bb5d9a58e7495d243d6b741078c8f7ee9c8813e473a323375702702b0afae1550c8341eedf5247627343a95240cb02e3e17d5dca16f8d8d3b2228e19c06399f8ec5c5e9dbe4caef6a0ea3ffb1d3c7eac03ae030e791fa12e537c80d56b55b764cadf27a8701052df1282ba8b5e3eb62b5dc7973ac40160e00722fa958d95102fc25c549d8c0e84bed95b7acb61ba65700c4de4feebf78d13b9682c52e937d23026fb4c6193e6644e2d3c99f91f4f39a8b9fc6d013f89c3793ef703987954dc0412b550652c01d922f525704d32d70d6d4079bc3551b563fb29577b3aecdc9505011701dddfd94830431e7a4918927ee44fb3831ce8c4513839e2deea1287f3fa1ab9b61a256c09637dbc7b4f0f8fbb783840f9c24526da883b0df0c473cf231656bd7bc1aaba7f321fec0971c8c2c3444bff2f55e1df7fea66ec3e440a612db9aa87bb505163a59e06b96d46f50d8120b92814ac5ab146bc78dbbf91065af26107815678ce6e33812e6bf3285d4ef3b7b04b076f21e7820dcbfdb4ad5218cf4ff6a65812d8fcb98ecc1e95e2fa58e3efe4ce26cd0bd400d6036ab2ad4f6c713082b5e3f1e04eb9e3b6c8f63f57953894b9e220e0130308e1fd91f72d398c1e7962ca2c31be83f31d6157633581a0a6910496de8d55d3d07090b6aa087159e388b7e7dec60f5d8a60d93ca2ae91296bd484d916bfaaa17c8f45ea4b1a91b37c82821199a2b7596672c37156d8701e7352aa48671d3b1bbbd2bd5f0a2268894a25b0cb2514af39c8743f8cce8ab4b523053739fd8a522222a09acf51ac704489cf17e4b7125455cb8f125b4d31af1eba1f8cf7f81a5a100a141a7ee72e8083e065616649c241f233645c5fc865d17f0285f5c52d9f45312c979bfb3ce5f2a1b951deddf280ffb3f370410cffd1583bfa90077835aa201a0712d1dcd1293ee177738b14e6b5e2a496d05220c3253bb6578d6aff774be91946a614dd7e879fb3dcf7451e0b9adb6a8c44f53c2c464bcc0019e9fad89cac7791a0a3f2974f759a9856351d4d2d7c5612c17cfc50f8479945df57716767b120a590f4bf656f4645029a525694d8a238446c5f5c2c1c995c09c1405b8b1eb9e0352ffdf766cc964f8dcf9f8f043dfab6d102cf4b298021abd78f1d9025fa1f8e1d710b38d9d1652f2d88d1305874ec41609b6617b65c5adb19b6295dc5c5da5fdf69f28144ea12f17c3c6fcce6b9b5157b3dfc969d6725fa5b098a4d9b1d31547ed4c9187452d281d0a5d456008caf1aa251fac8f950ca561982dc2dc908d3691ee3b6ad3ae3d22d002577264ca8e49c523bd51c4846be0d198ad9407bf6f7b82c79893eb2c05fe9981f687a97a4f01fe45ff8c8b7ecc551135cd960a0d6001ad35020be07ffb53cb9e731522ca8ae9364628914b9b8e8cc2f37f03393263603cc2b45295767eb0aac29b0930390eb89587ab2779d2e3decb8042acece725ba42eda650863f418f8d0d50d104e44fbbe5aa7389a4a144a8cecf00f45fb14c39112f9bfb56c0acbd44fa3ff261f5ce4acaa5134c2c1d0cca447040820c81ab1bcdc16aa075b7c68b10d06bbb7ce08b5b805e0238f24402cf24a4b4e00701935a0c68add3de090903f9b85b153cb179a582f57113bfc21c2093803f0cfa4d9d4672c2b05a24f7e4c34a8e9101b70303a7378b9c50b6cddd46814ef7fd73ef6923feceab8fc5aa8b0d185f2e83c7a99dcb1077c0ab5c1f5d5f01ba2f0420443f75c4417db9ebf1665efbb33dca224989920a64b44dc26f682cc77b4632c8454d49135e52503da855bc0f6ff8edc1145451a9772c06891f41064036b66c3119a0fc6e80dffeb65dc456108b7ca0296f4175fff3ed2b0f842cd46bd7e86f4c62dfaf1ddbf836263c00b34803de164983d0811cebfac86e7720c726d3048934c36c23189b02386a722ca9f0fe00233ab50db928d3bccea355cc681144b8b7edcaae4884d5a8f04425c0890ae2c74326e138066d8c05f4c82b29df99b034ea727afde590a1f2177ace3af99cfb1729d6539ce7f7f7314b046aab74497e63dd399e1f7d5f16517c23bd830d1fdee810f3c3b77573dd69c4b97d80d71fb5a632e00acdfa4f8e829faf3580d6a72c40b28a82172f8dcd4627663ebf6069736f21735fd84a226f427cd06bb055f94e7c92f31c48075a2955d82a5b9d2d0198ce0d4e131a112570a8ee40fb80462a81436a58e7db4e34b6e2c422e82f934ecda9949893da5730fc5c23c7c920f363f85ab28cc6a4206713c3152669b47efa8238fa826735f17b4e78750276162024ec85458cd5808e06f40dd9fd43775a456a3ff6cae90550d76d8b2899e0762ad9a371482b3e38083b1274708301d6346c22fea9bb4b73db490ff3ab05b2f7f9e187adef139a7794454b7300b8cc64d3ad76c0e4bc54e08833a4419251550655380d675bc91855aeb82585220bb97f03e976579c08f321b5f8f70988d3061f41465517d53ac571dbf1b24b94443d2e9a8e8a79b392b3d6a4ecdd7f626925c365ef6221305105ce9b5f5b6ecc5bed3d702bd4b7f5008aa8eb8c7aa3ade8ecf6251516fbefeea4e1082aa0e1848eddb31ffe44b04792d296054402826e4bd054e671f223e5557e4c94f89ca01c25c44f1a2ff2c05a70b43408250705e1b858bf0670679fdcd379203e36be3500dd981b1a6422c3cf15224f7fefdef0a5f225c5a09d15767598ecd9e262460bb33a4b5d09a64591efabc57c923d3be406979032ae0bc0997b65336a06dd75b253332ad6a8b63ef043f780a1b3fb6d0b6cad98b1ef4a02535eb39e14a866cfc5fc3a9c5deb2261300d71280ebe66a0776a151469551c3c5fa308757f956655278ec6330ae9e3625468c5f87e02cd9a6489910d4143c1f4ee13aa21a6859d907b788e28572fecee273d44e4a900fa0aa668dd861a60fb6b6b12c2c5ef3c8df1bd7ef5d4b0d1cdb8c15fffbb365b9784bd94abd001c6966216b9b67554ad7cb7f958b70092514f7800fc40244003e0fd1133a9b850fb17f4fcafde07fc87b07fb510670654a5d2d6fc9876ac74728ea41593beef003d6858786a52d3a40af7529596767c17000bfaf8dc52e871359f4ad8bf6e7b2853e5229bdf39657e213580294a5317c5df172865e1e17fe37093b585e04613f5f078f761b2b1752eb32983afda24b523af8851df9a02b37e77f543f18888a782a994a50563334282bf9cdfccc183fdf4fcd75ad86ee0d94f91ee2300a5befbccd14e03a77fc031a8cfe4f01e4c5290f5ac1da0d58ea054bd4837cfd93e5e34fc0eb16e48044ba76131f228d16cde9b0bb978ca7cdcd10653c358bdb26fdb723a530232c32ae0a4cecc06082f46e1c1d596bfe60621ad1e354e01e07b040cc7347c016653f44d926d13ca74e6cbc9d4ab4c99f4491c95c76fff5076b3936eb9d0a286b97c035ca88a3c6309f5febfd4cdaac869e4f58ed409b1e9eb4192fb2f9c2f12176d460fd98286c9d6df84598f260119fd29c63f800c07d8df83d5cc95f8c2fea2812e7890e8a0718bb1e031ecbebc0436dcf3e3b9a58bcc06b4c17f711f80fe1dffc3326a6eb6e00283055c6dabe20d311bfd5019591b7954f8163c9afad9ef8390a38f3582e0a79cdf0353de8eeb6b5f9f27b16ffdef7dd62869b4840ee226ccdce95e02c4545eb981b60571cd83f03dc5eaf8c97a0829a4318a9b3dc06c0e003db700b2260ff1fa8fee66890e637b109abb03ec901b05ca599775f48af50154c0e67d82bf0f558d7d3e0778dc38bea1eb5f74dc8d7f90abdf5511a424be66bf8b6a3cacb477d2e7ef4db68d2eba4d5289122d851f9501ba7e9c4957d8eba3be3fc8e785c4265a1d65c46f2809b70846c693864b169c9dcb78be26ea14b8613f145b01887222979a9e67aee5f800caa6f5c4229bdeefc901232ace6143c9865e4d9c07f51aa200afaf7e48a7d1d8faf366023beab12906ffcb3eaf72c0eb68075e4daf3c080e0c31911befc16f0cc4a09908bb7c1e26abab38bd7b788e1a09c0edf1a35a38d2ff1d3ed47fcdaae2f0934224694f5b56705b9409b6d3d64f3833b686f7576ec64bbdd6ff174e56c2d1edac0011f904681a73face26573fbba4e34652f7ae84acfb2fa5a5b3046f98178cd0831df7477de70e06a4c00e305f31aafc026ef064dd68fd3e4252b1b91d617b26c6d09b6891a00df68f105b5962e7f9d82da101dd595d286da721443b72b2aba2377f6e7772e33b3a5e3753da9c2578c5d1daab80187f55518c72a64ee150a7cb5649823c08c9f62cd7d020b45ec2cba8310db1a7785a46ab24785b4d54ff1660b5ca78e05a9a55edba9c60bf044737bc468101c4e8bd1480d749be5024adefca1d998abe33eaeb6b11fbb39da5d905fdd3f611b2e51517ccee4b8af72c2d948573505590d61a6783ab7278fc43fe55b1fcc0e7216444d3c8039bb8145ef1ce01c50e95a3f3feab0aee883fdb94cc13ee4d21c542aa795e18932228981690f4d4c57ca4db6eb5c092e29d8a05139d509a8aeb48baa1eb97a76e597a32b280b5e9d6c36859064c98ff96ef5126130264fa8d2f49213870d9fb036cff95da51f270311d9976208554e48ffd486470d0ecdb4e619ccbd8226147204baf8e235f54d8b1cba8fa34a9a4d055de515cdf180d2bb6739a175183c472e30b5c914d09eeb1b7dafd6872b38b48c6afc146101200e6e6a44fe5684e220adc11f5c403ddb15df8051e6bdef09117a3a5349938513776286473a3cf1d2788bb875052a2e6459fa7926da33380149c7f98d7700528a60c954e6f5ecb65842fde69d614be69eaa2040a4819ae6e756accf936e14c1e894489744a79c1f2c1eb295d13e2d767c09964b61f9cfe497649f712", + inIgnore: false, + midXOurs: "33a32d10066fa3963a9518a14d1bd1cb5ccaceaeaaeddb4d7aead90c08395bfd", + midXTheirs: "568146140669e69646a6ffeb3793e8010e2732209b4c34ec13e209a070109183", + midXShared: "a1017beaa8784f283dee185cd847ae3a327a981e62ae21e8c5face175fc97e9b", + midSharedSecret: "250b93570d411149105ab8cb0bc5079914906306368c23e9d77c2a33265b994c", + midInitiatorL: "4ec7daf7294a4a2c717442dd21cf2f052a3bfe9d535b55da0f66fecf87a27534", + midInitiatorP: "52ab4db9c4b06621f8ded3405691eb32465b1360d15a6b127ded4d15f9cde466", + midResponderL: "ba9906da802407ddedf6733e29f3996c62425e79d3cbfeebbd6ec4cdc7c976a8", + midResponderP: "ee661e18c97319ad071106bf35fe1085034832f70718d92f887932128b6100c7", + midSendGarbageTerm: "d4e3f18ac2e2095edb5c3b94236118ad", + midRecvGarbageTerm: "4faa6c4233d9fd53d170ede4172142a8", + outSessionID: "23f154ac43cfc59c4243e9fc68aeec8f19ad3942d74108e833b36f0dd3dcd357", + outCiphertext: "8da7de6ea7bf2a81a396a42880ba1f5756734c4821309ac9aeffa2a26ce86873b9dc4935a772de6ec5162c6d075b14536800fb174841153511bfb597e992e2fe8a450c4bce102cc550bb37fd564c4d60bf884e", + outCiphertextEndsWith: "", + }, + { + inIdx: 223, + inPrivOurs: "6c77432d1fda31e9f942f8af44607e10f3ad38a65f8a4bddae823e5eff90dc38", + inEllswiftOurs: "d2685070c1e6376e633e825296634fd461fa9e5bdf2109bcebd735e5a91f3e587c5cb782abb797fbf6bb5074fd1542a474f2a45b673763ec2db7fb99b737bbb9", + inEllswiftTheirs: "56bd0c06f10352c3a1a9f4b4c92f6fa2b26df124b57878353c1fc691c51abea77c8817daeeb9fa546b77c8daf79d89b22b0e1b87574ece42371f00237aa9d83a", + inInitiating: false, + inContents: "7e0e78eb6990b059e6cf0ded66ea93ef82e72aa2f18ac24f2fc6ebab561ae557420729da103f64cecfa20527e15f9fb669a49bbbf274ef0389b3e43c8c44e5f60bf2ac38e2b55e7ec4273dba15ba41d21f8f5b3ee1688b3c29951218caf847a97fb50d75a86515d445699497d968164bf740012679b8962de573be941c62b7ef", + inMultiply: 1, + inAad: "", + inIgnore: true, + midXOurs: "193d019db571162e52567e0cfdf9dd6964394f32769ae2edc4933b03b502d771", + midXTheirs: "2dd7b9cc85524f8670f695c3143ac26b45cebcabb2782a85e0fe15aee3956535", + midXShared: "5e35f94adfd57976833bffec48ef6dde983d18a55501154191ea352ef06732ee", + midSharedSecret: "1918b741ef5f9d1d7670b050c152b4a4ead2c31be9aecb0681c0cd4324150853", + midInitiatorL: "97124c56236425d792b1ec85e34b846e8d88c9b9f1d4f23ac6cdcc4c177055a0", + midInitiatorP: "8c71b468c61119415e3c1dfdd184134211951e2f623199629a46bff9673611f2", + midResponderL: "b43b8791b51ed682f56d64351601be28e478264411dcf963b14ee60b9ae427fa", + midResponderP: "794dde4b38ef04250c534a7fa638f2e8cc8b6d2c6110ec290ab0171fdf277d51", + midSendGarbageTerm: "cf2e25f23501399f30738d7eee652b90", + midRecvGarbageTerm: "225a477a28a54ea7671d2b217a9c29db", + outSessionID: "7ec02fea8c1484e3d0875f978c5f36d63545e2e4acf56311394422f4b66af612", + outCiphertext: "", + outCiphertextEndsWith: "729847a3e9eba7a5bff454b5de3b393431ee360736b6c030d7a5bd01d1203d2e98f528543fd2bf886ccaa1ada5e215a730a36b3f4abfc4e252c89eb01d9512f94916dae8a76bf16e4da28986ffe159090fe5267ee3394300b7ccf4dfad389a26321b3a3423e4594a82ccfbad16d6561ecb8772b0cb040280ff999a29e3d9d4fd", + }, + { + inIdx: 448, + inPrivOurs: "a6ec25127ca1aa4cf16b20084ba1e6516baae4d32422288e9b36d8bddd2de35a", + inEllswiftOurs: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff053d7ecca53e33e185a8b9be4e7699a97c6ff4c795522e5918ab7cd6b6884f67e683f3dc", + inEllswiftTheirs: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa7730be30000000000000000000000000000000000000000000000000000000000000000", + inInitiating: true, + inContents: "00cf68f8f7ac49ffaa02c4864fdf6dfe7bbf2c740b88d98c50ebafe32c92f3427f57601ffcb21a3435979287db8fee6c302926741f9d5e464c647eeb9b7acaeda46e00abd7506fc9a719847e9a7328215801e96198dac141a15c7c2f68e0690dd1176292a0dded04d1f548aad88f1aebdc0a8f87da4bb22df32dd7c160c225b843e83f6525d6d484f502f16d923124fc538794e21da2eb689d18d87406ecced5b9f92137239ed1d37bcfa7836641a83cf5e0a1cf63f51b06f158e499a459ede41c", + inMultiply: 1, + inAad: "", + inIgnore: false, + midXOurs: "02b225089255f7b02b20276cfe9779144df8fb1957b477bff3239d802d1256e9", + midXTheirs: "5232c4b6bde9d3d45d7b763ebd7495399bb825cc21de51011761cd81a51bdc84", + midXShared: "379223d2f1ea7f8a22043c4ce4122623098309e15b1ce58286ebe3d3bf40f4e1", + midSharedSecret: "dd210aa6629f20bb328e5d89daa6eb2ac3d1c658a725536ff154f31b536c23b2", + midInitiatorL: "393472f85a5cc6b0f02c4bd466db7a2dc5b91fc9dcb15c0dd6dc21116ece8bca", + midInitiatorP: "c80b87b793db47320b2795db66d331bd3021cc24e360d59d0fa8974f54687e0c", + midResponderL: "ef16a43d77e2b270b0a145ee1618d35f3c943cc7877d6cfcff2287d41692be39", + midResponderP: "20d4b62e2d982c61bb0cc39a93283d98af36530ef12331d44b2477b0e521b490", + midSendGarbageTerm: "fead69be77825a23daec377c362aa560", + midRecvGarbageTerm: "511d4980526c5e64aa7187462faeafdd", + outSessionID: "acb8f084ea763ddd1b92ac4ed23bf44de20b84ab677d4e4e6666a6090d40353d", + outCiphertext: "", + outCiphertextEndsWith: "77b4656934a82de1a593d8481f020194ddafd8cac441f9d72aeb8721e6a14f49698ca6d9b2b6d59d07a01aa552fd4d5b68d0d1617574c77dea10bfadbaa31b83885b7ceac2fd45e3e4a331c51a74e7b1698d81b64c87c73c5b9258b4d83297f9debc2e9aa07f8572ff434dc792b83ecf07b3197de8dc9cf7be56acb59c66cff5", + }, + { + inIdx: 673, + inPrivOurs: "0af952659ed76f80f585966b95ab6e6fd68654672827878684c8b547b1b94f5a", + inEllswiftOurs: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc81017fd92fd31637c26c906b42092e11cc0d3afae8d9019d2578af22735ce7bc469c72d", + inEllswiftTheirs: "9652d78baefc028cd37a6a92625b8b8f85fde1e4c944ad3f20e198bef8c02f19fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e91870", + inInitiating: false, + inContents: "5c6272ee55da855bbbf7b1246d9885aa7aa601a715ab86fa46c50da533badf82b97597c968293ae04e", + inMultiply: 97561, + inAad: "", + inIgnore: false, + midXOurs: "4b1767466fe2fb8deddf2dc52cc19c7e2032007e19bfb420b30a80152d0f22d6", + midXTheirs: "64c383e0e78ac99476ddff2061683eeefa505e3666673a1371342c3e6c26981d", + midXShared: "5bcfeac98d87e87e158bf839f1269705429f7af2a25b566a25811b5f9aef9560", + midSharedSecret: "3568f2aea2e14ef4ee4a3c2a8b8d31bc5e3187ba86db10739b4ff8ec92ff6655", + midInitiatorL: "c7df866a62b7d404eb530b2be245a7aece0fb4791402a1de8f33530cbf777cc1", + midInitiatorP: "8f732e4aae2ba9314e0982492fa47954de9c189d92fbc549763b27b1b47642ce", + midResponderL: "992085edfecb92c62a3a7f96ea416f853f34d0dfe065b966b6968b8b87a83081", + midResponderP: "c5ba5eaf9e1c807154ebab3ea472499e815a7be56dfaf0c201cf6e91ffeca8e6", + midSendGarbageTerm: "5e2375ac629b8df1e4ff3617c6255a70", + midRecvGarbageTerm: "70bcbffcb62e4d29d2605d30bceef137", + outSessionID: "7332e92a3f9d2792c4d444fac5ed888c39a073043a65eefb626318fd649328f8", + outCiphertext: "", + outCiphertextEndsWith: "657a4a19711ce593c3844cb391b224f60124aba7e04266233bc50cafb971e26c7716b76e98376448f7d214dd11e629ef9a974d60e3770a695810a61c4ba66d78b936ee7892b98f0b48ddae9fcd8b599dca1c9b43e9b95e0226cf8d4459b8a7c2c4e6db80f1d58c7b20dd7208fa5c1057fb78734223ee801dbd851db601fee61e", + }, + { + inIdx: 1024, + inPrivOurs: "f90e080c64b05824c5a24b2501d5aeaf08af3872ee860aa80bdcd430f7b63494", + inEllswiftOurs: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff115173765dc202cf029ad3f15479735d57697af12b0131dd21430d5772e4ef11474d58b9", + inEllswiftTheirs: "12a50f3fafea7c1eeada4cf8d33777704b77361453afc83bda91eef349ae044d20126c6200547ea5a6911776c05dee2a7f1a9ba7dfbabbbd273c3ef29ef46e46", + inInitiating: true, + inContents: "5f67d15d22ca9b2804eeab0a66f7f8e3a10fa5de5809a046084348cbc5304e843ef96f59a59c7d7fdfe5946489f3ea297d941bac326225df316a25fc90f0e65b0d31a9c497e960fdbf8c482516bc8a9c1c77b7f6d0e1143810c737f76f9224e6f2c9af5186b4f7259c7e8d165b6e4fe3d38a60bdbdd4d06ecdcaaf62086070dbb68686b802d53dfd7db14b18743832605f5461ad81e2af4b7e8ff0eff0867a25b93cec7becf15c43131895fed09a83bf1ee4a87d44dd0f02a837bf5a1232e201cb882734eb9643dc2dc4d4e8b5690840766212c7ac8f38ad8a9ec47c7a9b3e022ae3eb6a32522128b518bd0d0085dd81c5", + inMultiply: 69615, + inAad: "", + inIgnore: true, + midXOurs: "8b8de966150bf872b4b695c9983df519c909811954d5d76e99ed0d5f1860247b", + midXTheirs: "eef379db9bd4b1aa90fc347fad33f7d53083389e22e971036f59f4e29d325ac2", + midXShared: "0a402d812314646ccc2565c315d1429ec1ed130ff92ff3f48d948f29c3762cf1", + midSharedSecret: "e25461fb0e4c162e18123ecde88342d54d449631e9b75a266fd9260c2bb2f41d", + midInitiatorL: "97771ce2ce17a25c3d65bf9f8e4acb830dce8d41392be3e4b8ed902a3106681a", + midInitiatorP: "2e7022b4eae9152942f68160a93e25d3e197a557385594aa587cb5e431bb470d", + midResponderL: "613f85a82d783ce450cfd7e91a027fcc4ad5610872f83e4dbe9e2202184c6d6e", + midResponderP: "cb5de4ed1083222e381401cf88e3167796bc9ab5b8aa1f27b718f39d1e6c0e87", + midSendGarbageTerm: "b709dea25e0be287c50e3603482c2e98", + midRecvGarbageTerm: "1f677e9d7392ebe3633fd82c9efb0f16", + outSessionID: "889f339285564fd868401fac8380bb9887925122ec8f31c8ae51ce067def103b", + outCiphertext: "", + outCiphertextEndsWith: "7c4b9e1e6c1ce69da7b01513cdc4588fd93b04dafefaf87f31561763d906c672bac3dfceb751ebd126728ac017d4d580e931b8e5c7d5dfe0123be4dc9b2d2238b655c8a7fadaf8082c31e310909b5b731efc12f0a56e849eae6bfeedcc86dd27ef9b91d159256aa8e8d2b71a311f73350863d70f18d0d7302cf551e4303c7733", + }, + } + + for _, test := range tests { + inInitiating := test.inInitiating + + // We need to convert the FieldVal into a ModNScalar so that we can use + // the ScalarBaseMultNonConst. + inPrivOurs := setHex(test.inPrivOurs) + inPrivOursBytes := inPrivOurs.Bytes() + + var inPrivOursScalar btcec.ModNScalar + overflow := inPrivOursScalar.SetBytes(inPrivOursBytes) + if overflow == 1 { + t.Fatalf("unexpected reduction") + } + + var inPubOurs btcec.JacobianPoint + btcec.ScalarBaseMultNonConst(&inPrivOursScalar, &inPubOurs) + inPubOurs.ToAffine() + + midXOurs := setHex(test.midXOurs) + if !midXOurs.Equals(&inPubOurs.X) { + t.Fatalf("expected mid-state to match our public key") + } + + // ellswift_decode takes in ellswift_bytes and returns a proper key. + // 1. convert from hex to bytes + bytesEllswiftOurs, err := hex.DecodeString(test.inEllswiftOurs) + if err != nil { + t.Fatalf("unexpected error decoding string") + } + + uEllswiftOurs := bytesEllswiftOurs[:32] + tEllswiftOurs := bytesEllswiftOurs[32:] + + var ( + uEllswiftOursFV btcec.FieldVal + tEllswiftOursFV btcec.FieldVal + ) + + truncated := uEllswiftOursFV.SetByteSlice(uEllswiftOurs) + if truncated { + uEllswiftOursFV.Normalize() + } + + truncated = tEllswiftOursFV.SetByteSlice(tEllswiftOurs) + if truncated { + tEllswiftOursFV.Normalize() + } + + xEllswiftOurs := btcec.XSwiftEC(&uEllswiftOursFV, &tEllswiftOursFV) + if !midXOurs.Equals(xEllswiftOurs) { + t.Fatalf("expected mid-state to match decoded ellswift key") + } + + bytesEllswiftTheirs, err := hex.DecodeString(test.inEllswiftTheirs) + if err != nil { + t.Fatalf("unexpected error decoding string") + } + + uEllswiftTheirs := bytesEllswiftTheirs[:32] + tEllswiftTheirs := bytesEllswiftTheirs[32:] + + var ( + uEllswiftTheirsFV btcec.FieldVal + tEllswiftTheirsFV btcec.FieldVal + ) + + truncated = uEllswiftTheirsFV.SetByteSlice(uEllswiftTheirs) + if truncated { + uEllswiftTheirsFV.Normalize() + } + + truncated = tEllswiftTheirsFV.SetByteSlice(tEllswiftTheirs) + if truncated { + tEllswiftTheirsFV.Normalize() + } + + xEllswiftTheirs := btcec.XSwiftEC(&uEllswiftTheirsFV, &tEllswiftTheirsFV) + + midXTheirs := setHex(test.midXTheirs) + if !midXTheirs.Equals(xEllswiftTheirs) { + t.Fatalf("expected mid-state to match decoded ellswift key") + } + + privKeyOurs, _ := btcec.PrivKeyFromBytes((*inPrivOursBytes)[:]) + + var bytesEllswiftTheirs64 [64]byte + copy(bytesEllswiftTheirs64[:], bytesEllswiftTheirs) + + xShared, err := btcec.EllswiftECDHXOnly(bytesEllswiftTheirs64, privKeyOurs) + if err != nil { + t.Fatalf("unexpected error when computing shared x") + } + + var xSharedFV btcec.FieldVal + overflow = xSharedFV.SetBytes(&xShared) + if overflow == 1 { + t.Fatalf("unexpected truncation") + } + + midXShared := setHex(test.midXShared) + + if !midXShared.Equals(&xSharedFV) { + t.Fatalf("expected mid-state x shared") + } + + var bytesEllswiftOurs64 [64]byte + copy(bytesEllswiftOurs64[:], bytesEllswiftOurs) + + sharedSecret, err := btcec.V2Ecdh( + privKeyOurs, bytesEllswiftTheirs64, bytesEllswiftOurs64, inInitiating, + ) + if err != nil { + t.Fatalf("unexpected error when calculating shared secret") + } + + midShared, err := hex.DecodeString(test.midSharedSecret) + if err != nil { + t.Fatalf("unexpected hex decode failure") + } + + if !bytes.Equal(midShared, sharedSecret[:]) { + t.Fatalf("expected mid shared secret") + } + + p := NewPeer() + + buf := bytes.NewBuffer(nil) + p.UseReadWriter(buf) + + err = p.createV2Ciphers(midShared, inInitiating, wire.MainNet) + if err != nil { + t.Fatalf("error initiating v2 transport") + } + + midInitiatorL, err := hex.DecodeString(test.midInitiatorL) + if err != nil { + t.Fatalf("unexpected error decoding midInitiatorL") + } + + if !bytes.Equal(midInitiatorL, p.initiatorL) { + t.Fatalf("expected mid-state initiatorL to match computed value") + } + + midInitiatorP, err := hex.DecodeString(test.midInitiatorP) + if err != nil { + t.Fatalf("unexpected error decoding midInitiatorP") + } + + if !bytes.Equal(midInitiatorP, p.initiatorP) { + t.Fatalf("expected mid-state initiatorP to match computed value") + } + + midResponderL, err := hex.DecodeString(test.midResponderL) + if err != nil { + t.Fatalf("unexpected error decoding midResponderL") + } + + if !bytes.Equal(midResponderL, p.responderL) { + t.Fatalf("expected mid-state responderL to match computed value") + } + + midResponderP, err := hex.DecodeString(test.midResponderP) + if err != nil { + t.Fatalf("unexpected error decoding midResponderP") + } + + if !bytes.Equal(midResponderP, p.responderP) { + t.Fatalf("expected mid-state responderP to match computed value") + } + + midSendGarbageTerm, err := hex.DecodeString(test.midSendGarbageTerm) + if err != nil { + t.Fatalf("unexpected error decoding midSendGarbageTerm") + } + + if !bytes.Equal(midSendGarbageTerm, p.sendGarbageTerm) { + t.Fatalf("expected mid-state sendGarbageTerm to match computed value") + } + + midRecvGarbageTerm, err := hex.DecodeString(test.midRecvGarbageTerm) + if err != nil { + t.Fatalf("unexpected error decoding midRecvGarbageTerm") + } + + if !bytes.Equal(midRecvGarbageTerm, p.recvGarbageTerm) { + t.Fatalf("expected mid-state recvGarbageTerm to match computed value") + } + + outSessionID, err := hex.DecodeString(test.outSessionID) + if err != nil { + t.Fatalf("unexpected error decoding outSessionID") + } + + if !bytes.Equal(outSessionID, p.sessionID) { + t.Fatalf("expected sessionID to match computed value") + } + + for i := 0; i < test.inIdx; i++ { + _, _, err = p.V2EncPacket([]byte{}, []byte{}, false) + if err != nil { + t.Fatalf("unexpected error while encrypting packet") + } + } + + initialContents, err := hex.DecodeString(test.inContents) + if err != nil { + t.Fatalf("unexpected error decoding contents") + } + + aad, err := hex.DecodeString(test.inAad) + if err != nil { + t.Fatalf("unexpected error decoding aad") + } + + var contents []byte + + copy(contents, initialContents) + + for i := 0; i < test.inMultiply; i++ { + contents = append(contents, initialContents...) + } + + ciphertext, _, err := p.V2EncPacket(contents, aad, test.inIgnore) + if err != nil { + t.Fatalf("unexpected error when encrypting packet") + } + + if len(test.outCiphertext) != 0 { + outCiphertextBytes, err := hex.DecodeString(test.outCiphertext) + if err != nil { + t.Fatalf("unexpected error decoding outCiphertext") + } + + if !bytes.Equal(outCiphertextBytes, ciphertext) { + t.Fatalf("ciphertext mismatch") + } + } + + if len(test.outCiphertextEndsWith) != 0 { + ciphertextHex := hex.EncodeToString(ciphertext) + if !strings.HasSuffix(ciphertextHex, test.outCiphertextEndsWith) { + t.Fatalf("suffix mismatch") + } + } + } +} From 4f34144c91cdc6709e93255174b8fb7853bc8dad Mon Sep 17 00:00:00 2001 From: Eugene Siegel Date: Thu, 25 Jul 2024 11:06:57 -0400 Subject: [PATCH 3/4] wire: introduce encoding/decoding funcs for v2 messages --- wire/message.go | 181 +++++++++++++++++++++++++++++++++++++++++++++++ wire/protocol.go | 2 + 2 files changed, 183 insertions(+) diff --git a/wire/message.go b/wire/message.go index 1f412fa6fa..6a1724b973 100644 --- a/wire/message.go +++ b/wire/message.go @@ -61,6 +61,62 @@ const ( CmdSendAddrV2 = "sendaddrv2" ) +var ( + v2MessageIDs = map[uint8]string{ + 1: CmdAddr, + 2: CmdBlock, + 5: CmdFeeFilter, + 6: CmdFilterAdd, + 7: CmdFilterClear, + 8: CmdFilterLoad, + 9: CmdGetBlocks, + 11: CmdGetData, + 12: CmdGetHeaders, + 13: CmdHeaders, + 14: CmdInv, + 15: CmdMemPool, + 16: CmdMerkleBlock, + 17: CmdNotFound, + 18: CmdPing, + 19: CmdPong, + 21: CmdTx, + 22: CmdGetCFilters, + 23: CmdCFilter, + 24: CmdGetCFHeaders, + 25: CmdCFHeaders, + 26: CmdGetCFCheckpt, + 27: CmdCFCheckpt, + 28: CmdAddrV2, + } + + v2Messages = map[string]uint8{ + CmdAddr: 1, + CmdBlock: 2, + CmdFeeFilter: 5, + CmdFilterAdd: 6, + CmdFilterClear: 7, + CmdFilterLoad: 8, + CmdGetBlocks: 9, + CmdGetData: 11, + CmdGetHeaders: 12, + CmdHeaders: 13, + CmdInv: 14, + CmdMemPool: 15, + CmdMerkleBlock: 16, + CmdNotFound: 17, + CmdPing: 18, + CmdPong: 19, + CmdTx: 21, + CmdGetCFilters: 22, + CmdCFilter: 23, + CmdGetCFHeaders: 24, + CmdCFHeaders: 25, + CmdGetCFCheckpt: 26, + CmdCFCheckpt: 27, + CmdAddrV2: 28, + } +) + // MessageEncoding represents the wire message encoding format to be used. type MessageEncoding uint32 @@ -270,6 +326,80 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) erro return err } +// WriteV2MessageN writes a Message to the passed Writer using the bip324 +// v2 encoding. +func WriteV2MessageN(w io.Writer, msg Message, pver uint32, + encoding MessageEncoding) (int, error) { + + var totalBytes int + + cmd := msg.Command() + if len(cmd) > CommandSize { + str := fmt.Sprintf("command [%s] is too long [max %v]", + cmd, CommandSize) + return totalBytes, messageError("WriteMessage", str) + } + + index, exists := v2Messages[cmd] + if !exists { + var command [CommandSize]byte + copy(command[:], cmd) + hw := bytes.NewBuffer(make([]byte, 0, CommandSize+1)) + writeElements(hw, byte(0x00), command) + + n, err := w.Write(hw.Bytes()) + if err != nil { + return 0, err + } + + totalBytes += n + } else { + hw := bytes.NewBuffer(make([]byte, 0, 1)) + writeElement(hw, index) + + n, err := w.Write(hw.Bytes()) + if err != nil { + return 0, err + } + + totalBytes += n + } + + var bw bytes.Buffer + err := msg.BtcEncode(&bw, pver, encoding) + if err != nil { + return totalBytes, err + } + + payload := bw.Bytes() + lenp := len(payload) + + // Enforce maximum overall message payload. + if lenp > MaxMessagePayload { + str := fmt.Sprintf("message payload is too large - encoded "+ + "%d bytes, but maximum message payload is %d bytes", + lenp, MaxMessagePayload) + return totalBytes, messageError("WriteMessage", str) + } + + mpl := msg.MaxPayloadLength(pver) + if uint32(lenp) > mpl { + str := fmt.Sprintf("message payload is too large - encoded "+ + "%d bytes, but maximum message payload size for "+ + "messages of type [%s] is %d.", lenp, cmd, mpl) + return totalBytes, messageError("WriteMessage", str) + } + + if len(payload) > 0 { + n, err := w.Write(payload) + totalBytes += n + + return totalBytes, err + } + + return totalBytes, nil +} + // WriteMessageWithEncodingN writes a bitcoin Message to w including the // necessary header information and returns the number of bytes written. // This function is the same as WriteMessageN except it also allows the caller @@ -346,6 +476,57 @@ func WriteMessageWithEncodingN(w io.Writer, msg Message, pver uint32, return totalBytes, err } +// ReadV2MessageN takes the passed plaintext and attempts to construct a +// Message from the bytes using the bip324 v2 encoding. +func ReadV2MessageN(plaintext []byte, pver uint32, enc MessageEncoding) ( + Message, []byte, error) { + + if len(plaintext) == 0 { + return nil, nil, fmt.Errorf("invalid plaintext length") + } + + var msgCmd string + + // If the first byte is 0x00, read the next 12 bytes to determine what + // message this is. + if plaintext[0] == 0x00 { + if len(plaintext) < CommandSize+1 { + return nil, nil, fmt.Errorf("invalid plaintext length") + } + + // Slice off the first 0x00 and the trailing 0x00 bytes. + var command [CommandSize]byte + copy(command[:], plaintext[1:CommandSize+1]) + + msgCmd = string(bytes.TrimRight(command[:], "\x00")) + + plaintext = plaintext[CommandSize+1:] + } else { + // The first byte denotes what message this is. + msgCmd = v2MessageIDs[plaintext[0]] + + plaintext = plaintext[1:] + } + + msg, err := makeEmptyMessage(msgCmd) + if err != nil { + return nil, nil, err + } + + mpl := msg.MaxPayloadLength(pver) + if len(plaintext) > int(mpl) { + return nil, nil, fmt.Errorf("payload exceeds max length") + } + + buf := bytes.NewBuffer(plaintext) + err = msg.BtcDecode(buf, pver, enc) + if err != nil { + return nil, nil, err + } + + return msg, plaintext, nil +} + // ReadMessageWithEncodingN reads, validates, and parses the next bitcoin Message // from r for the provided protocol version and bitcoin network. It returns the // number of bytes read in addition to the parsed Message and raw bytes which diff --git a/wire/protocol.go b/wire/protocol.go index baeec05369..e7e5aa9197 100644 --- a/wire/protocol.go +++ b/wire/protocol.go @@ -103,6 +103,8 @@ const ( // SFNodeNetWorkLimited is a flag used to indicate a peer supports serving // the last 288 blocks. SFNodeNetworkLimited = 1 << 10 + + SFNodeP2PV2 = 1 << 11 ) // Map of service flags back to their constant names for pretty printing. From b19bba0d794590b5e9e4a05e571acacf34c01584 Mon Sep 17 00:00:00 2001 From: Eugene Siegel Date: Thu, 25 Jul 2024 11:17:24 -0400 Subject: [PATCH 4/4] server.go+peer: enable v2 functionality in peer code This adds the v2 software flag to the list of services btcd supports and adds functionality to optionally use v2 transport if the peer supports it. --- go.mod | 11 ++++-- go.sum | 51 +++---------------------- peer/peer.go | 103 +++++++++++++++++++++++++++++++++++++++++++++------ server.go | 3 +- 4 files changed, 108 insertions(+), 60 deletions(-) diff --git a/go.mod b/go.mod index 1f445d9065..c62a2f6e5d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 + github.com/btcsuite/btcd/v2transport v1.0.0 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 @@ -16,8 +17,8 @@ require ( github.com/jrick/logrotate v1.0.0 github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - golang.org/x/crypto v0.22.0 - golang.org/x/sys v0.19.0 + golang.org/x/crypto v0.25.0 + golang.org/x/sys v0.22.0 ) require ( @@ -31,6 +32,10 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) +replace github.com/btcsuite/btcd/btcec/v2 => ./btcec + +replace github.com/btcsuite/btcd/v2transport => ./v2transport + // The retract statements below fixes an accidental push of the tags of a btcd // fork. retract ( @@ -63,4 +68,4 @@ retract ( v0.13.0-beta ) -go 1.17 +go 1.23.2 diff --git a/go.sum b/go.sum index bb666c89de..a5267985c1 100644 --- a/go.sum +++ b/go.sum @@ -3,15 +3,10 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA 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.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -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.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= -github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= 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.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= -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.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= @@ -88,33 +83,19 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -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/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 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-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/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/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= @@ -124,34 +105,14 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w 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.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -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= diff --git a/peer/peer.go b/peer/peer.go index 195fc0b4fe..d4833ba73d 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -21,6 +21,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/v2transport" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/go-socks/socks" "github.com/davecgh/go-spew/spew" @@ -289,6 +290,10 @@ type Config struct { // scenarios where the stall behavior isn't important to the system // under test. DisableStallHandler bool + + // UsingV2Conn is defined if and only if we accept and attempt to make + // v2 connections. + UsingV2Conn bool } // minUint32 is a helper function to return the minimum of two uint32s. @@ -463,6 +468,8 @@ type Peer struct { witnessEnabled bool sendAddrV2 bool + V2Transport *v2transport.Peer + wireEncoding wire.MessageEncoding knownInventory lru.Cache @@ -1067,8 +1074,32 @@ func (p *Peer) handlePongMsg(msg *wire.MsgPong) { // readMessage reads the next bitcoin message from the peer with logging. func (p *Peer) readMessage(encoding wire.MessageEncoding) (wire.Message, []byte, error) { - n, msg, buf, err := wire.ReadMessageWithEncodingN(p.conn, - p.ProtocolVersion(), p.cfg.ChainParams.Net, encoding) + var ( + n int + msg wire.Message + buf []byte + plaintext []byte + err error + ) + + if p.cfg.UsingV2Conn { + plaintext, err = p.V2Transport.V2ReceivePacket(nil) + if err != nil { + fmt.Printf("failed to read packet: %v\n", err) + return nil, nil, err + } + + msg, buf, err = wire.ReadV2MessageN( + plaintext, p.ProtocolVersion(), encoding, + ) + n = len(plaintext) + atomic.AddUint64(&p.bytesReceived, uint64(n)) + } else { + n, msg, buf, err = wire.ReadMessageWithEncodingN( + p.conn, p.ProtocolVersion(), p.cfg.ChainParams.Net, encoding, + ) + } + atomic.AddUint64(&p.bytesReceived, uint64(n)) if p.cfg.Listeners.OnRead != nil { p.cfg.Listeners.OnRead(p, n, msg, err) @@ -1105,6 +1136,25 @@ func (p *Peer) writeMessage(msg wire.Message, enc wire.MessageEncoding) error { return nil } + var ( + buf bytes.Buffer + n int + err error + ) + + if p.cfg.UsingV2Conn { + _, err = wire.WriteV2MessageN(&buf, msg, p.ProtocolVersion(), enc) + if err != nil { + return err + } + + _, n, err = p.V2Transport.V2EncPacket(buf.Bytes(), nil, false) + } else { + n, err = wire.WriteMessageWithEncodingN( + p.conn, msg, p.ProtocolVersion(), p.cfg.ChainParams.Net, enc, + ) + } + // Use closures to log expensive operations so they are only run when // the logging level requires it. log.Debugf("%v", newLogClosure(func() string { @@ -1120,18 +1170,9 @@ func (p *Peer) writeMessage(msg wire.Message, enc wire.MessageEncoding) error { return spew.Sdump(msg) })) log.Tracef("%v", newLogClosure(func() string { - var buf bytes.Buffer - _, err := wire.WriteMessageWithEncodingN(&buf, msg, p.ProtocolVersion(), - p.cfg.ChainParams.Net, enc) - if err != nil { - return err.Error() - } return spew.Sdump(buf.Bytes()) })) - // Write the message to the peer. - n, err := wire.WriteMessageWithEncodingN(p.conn, msg, - p.ProtocolVersion(), p.cfg.ChainParams.Net, enc) atomic.AddUint64(&p.bytesSent, uint64(n)) if p.cfg.Listeners.OnWrite != nil { p.cfg.Listeners.OnWrite(p, n, msg, err) @@ -2215,6 +2256,21 @@ func (p *Peer) waitToFinishNegotiation(pver uint32) error { // that btcd does not implement but bitcoind does. // 6. If remote peer sent sendaddrv2 above, wait until receipt of verack. func (p *Peer) negotiateInboundProtocol() error { + if p.cfg.UsingV2Conn { + // TODO: Change garbageLen to be random. + err := p.V2Transport.RespondV2Handshake(1, p.cfg.ChainParams.Net) + if err != v2transport.ErrUseV1Protocol { + // This is potentially a v2 connection. V1 connections can use the + // legacy negotiation code below immediately. + err = p.V2Transport.CompleteHandshake( + false, nil, p.cfg.ChainParams.Net, + ) + if err != nil { + return err + } + } + } + if err := p.readRemoteVersionMsg(); err != nil { return err } @@ -2253,6 +2309,19 @@ func (p *Peer) negotiateInboundProtocol() error { // in the inbound case. // 6. If sendaddrv2 was received, wait for receipt of verack. func (p *Peer) negotiateOutboundProtocol() error { + if p.cfg.UsingV2Conn { + // TODO: random value for garbage len? + if err := p.V2Transport.InitiateV2Handshake(1); err != nil { + return err + } + + if err := p.V2Transport.CompleteHandshake( + true, nil, p.cfg.ChainParams.Net, + ); err != nil { + return err + } + } + if err := p.writeLocalVersionMsg(); err != nil { return err } @@ -2327,6 +2396,10 @@ func (p *Peer) AssociateConnection(conn net.Conn) { p.conn = conn p.timeConnected = time.Now() + if p.cfg.UsingV2Conn { + p.V2Transport.UseReadWriter(conn) + } + if p.inbound { p.addr = p.conn.RemoteAddr().String() @@ -2401,6 +2474,14 @@ func newPeerBase(origCfg *Config, inbound bool) *Peer { services: cfg.Services, protocolVersion: cfg.ProtocolVersion, } + + if p.cfg.UsingV2Conn && p.Services()&wire.SFNodeP2PV2 == wire.SFNodeP2PV2 { + p.V2Transport = v2transport.NewPeer() + } else { + // TODO: Hack, change. + p.cfg.UsingV2Conn = false + } + return &p } diff --git a/server.go b/server.go index 66794e4bb7..41b2ac5144 100644 --- a/server.go +++ b/server.go @@ -45,7 +45,7 @@ const ( // defaultServices describes the default services that are supported by // the server. defaultServices = wire.SFNodeNetwork | wire.SFNodeNetworkLimited | - wire.SFNodeBloom | wire.SFNodeWitness | wire.SFNodeCF + wire.SFNodeBloom | wire.SFNodeWitness | wire.SFNodeCF | wire.SFNodeP2PV2 // defaultRequiredServices describes the default services that are // required to be supported by outbound peers. @@ -2164,6 +2164,7 @@ func newPeerConfig(sp *serverPeer) *peer.Config { ProtocolVersion: peer.MaxProtocolVersion, TrickleInterval: cfg.TrickleInterval, DisableStallHandler: cfg.DisableStallHandler, + UsingV2Conn: true, } }