sweeptimelockmanual: refactor and implement new ECDH format

We need to implement the new ECDH based revocation root format.
pull/38/head
Oliver Gugger 3 years ago
parent 9000e82973
commit a1d6ae8bae
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -26,7 +26,7 @@ import (
const (
defaultAPIURL = "https://blockstream.info/api"
version = "0.9.5"
version = "0.9.6"
na = "n/a"
Commit = ""

@ -4,7 +4,6 @@ import (
"bytes"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
@ -165,51 +164,17 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
commitPoint *btcec.PublicKey
)
for i := uint32(0); i < maxKeys; i++ {
// The easy part first, let's derive the delay base point.
delayPath := []uint32{
lnd.HardenedKey(uint32(keychain.KeyFamilyDelayBase)), 0,
i,
}
delayPrivKey, err := lnd.PrivKeyFromPath(baseKey, delayPath)
if err != nil {
return err
}
// Get the revocation base point first so we can calculate our
// commit point.
revPath := []uint32{
lnd.HardenedKey(uint32(
keychain.KeyFamilyRevocationRoot,
)), 0, i,
}
revRoot, err := lnd.ShaChainFromPath(baseKey, revPath)
if err != nil {
return err
}
// We now have everything to brute force the lock script. This
// will take a long while as we both have to go through commit
// points and CSV values.
csvTimeout, script, scriptHash, commitPoint, err =
bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot,
lockScript, maxCsvTimeout,
)
csvTimeout, script, scriptHash, commitPoint, delayDesc, err = tryKey(
baseKey, remoteRevPoint, maxCsvTimeout, lockScript, i,
)
if err == nil {
delayDesc = &keychain.KeyDescriptor{
PubKey: delayPrivKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyDelayBase,
Index: i,
},
}
log.Infof("Found keys at index %d with CSV timeout %d",
i, csvTimeout)
break
}
if i != 0 && i%20 == 0 {
fmt.Printf("Tried %d of %d keys.", i, maxKeys)
}
log.Infof("Tried %d of %d keys.", i+1, maxKeys)
}
// Did we find what we looked for or did we just exhaust all
@ -318,6 +283,115 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
}
func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
maxCsvTimeout uint16, lockScript []byte, idx uint32) (int32, []byte,
[]byte, *btcec.PublicKey, *keychain.KeyDescriptor, error) {
// The easy part first, let's derive the delay base point.
delayPath := []uint32{
lnd.HardenedKey(uint32(keychain.KeyFamilyDelayBase)),
0, idx,
}
delayPrivKey, err := lnd.PrivKeyFromPath(baseKey, delayPath)
if err != nil {
return 0, nil, nil, nil, nil, err
}
// Get the revocation base point first, so we can calculate our
// commit point. We start with the old way where the revocation index
// was the same as the other indices.
revPath := []uint32{
lnd.HardenedKey(uint32(
keychain.KeyFamilyRevocationRoot,
)), 0, idx,
}
revRoot, err := lnd.ShaChainFromPath(baseKey, revPath, nil)
if err != nil {
return 0, nil, nil, nil, nil, err
}
// We now have everything to brute force the lock script. This
// will take a long while as we both have to go through commit
// points and CSV values.
csvTimeout, script, scriptHash, commitPoint, err := bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot, lockScript,
maxCsvTimeout,
)
if err == nil {
return csvTimeout, script, scriptHash, commitPoint,
&keychain.KeyDescriptor{
PubKey: delayPrivKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyDelayBase,
Index: idx,
},
}, nil
}
// Now let's try with the new format where the index is one larger than
// the other indices.
revPath = []uint32{
lnd.HardenedKey(uint32(
keychain.KeyFamilyRevocationRoot,
)), 0, idx + 1,
}
revRoot2, err := lnd.ShaChainFromPath(baseKey, revPath, nil)
if err != nil {
return 0, nil, nil, nil, nil, err
}
// We now have everything to brute force the lock script. This
// will take a long while as we both have to go through commit
// points and CSV values.
csvTimeout, script, scriptHash, commitPoint, err = bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot2, lockScript,
maxCsvTimeout,
)
if err == nil {
return csvTimeout, script, scriptHash, commitPoint,
&keychain.KeyDescriptor{
PubKey: delayPrivKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyDelayBase,
Index: idx,
},
}, nil
}
// Now we try the same with the new revocation producer format.
multiSigPath := []uint32{
lnd.HardenedKey(uint32(keychain.KeyFamilyMultiSig)),
0, idx,
}
multiSigPrivKey, err := lnd.PrivKeyFromPath(
baseKey, multiSigPath,
)
revRoot3, err := lnd.ShaChainFromPath(
baseKey, revPath, multiSigPrivKey.PubKey(),
)
if err != nil {
return 0, nil, nil, nil, nil, err
}
csvTimeout, script, scriptHash, commitPoint, err = bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot3, lockScript,
maxCsvTimeout,
)
if err == nil {
return csvTimeout, script, scriptHash, commitPoint,
&keychain.KeyDescriptor{
PubKey: delayPrivKey.PubKey(),
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyDelayBase,
Index: idx,
},
}, nil
}
return 0, nil, nil, nil, nil, fmt.Errorf("target script not derived")
}
func bruteForceDelayPoint(delayBase, revBase *btcec.PublicKey,
revRoot *shachain.RevocationProducer, lockScript []byte,
maxCsvTimeout uint16) (int32, []byte, []byte, *btcec.PublicKey, error) {

@ -0,0 +1,66 @@
package main
import (
"encoding/hex"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/guggero/chantools/lnd"
)
var sweepTimeLockManualCases = []struct {
baseKey string
keyIndex uint32
timeLockAddr string
remoteRevPubKey string
}{{
// New format with ECDH revocation root.
baseKey: "tprv8dgoXnQWBN4CGGceRYMW495kWcrUZKZVFwMmbzpduFp1D4pi" +
"3B2t37zTG5Fx66XWPDQYi3Q5vqDgmmZ5ffrqZ9H4s2EhJu9WaJjY3SKaWDK",
keyIndex: 7,
timeLockAddr: "bcrt1qf9zv4qtxh27c954rhlzg4tx58xh0vgssuu0csrlep0jdnv" +
"lx9xesmcl5qx",
remoteRevPubKey: "03235261ed5aaaf9fec0e91d5e1a4d17f1a2c7442f1c43806d32" +
"c9bd34abd002a3",
}, {
// Old format with plain private key as revocation root.
baseKey: "tprv8dgoXnQWBN4CGGceRYMW495kWcrUZKZVFwMmbzpduFp1D4pi" +
"3B2t37zTG5Fx66XWPDQYi3Q5vqDgmmZ5ffrqZ9H4s2EhJu9WaJjY3SKaWDK",
keyIndex: 6,
timeLockAddr: "bcrt1qa5rrlswxefc870k7rsza5hhqd37uytczldjk5t0vzd95u9" +
"hs8xlsfdc3zf",
remoteRevPubKey: "03e82cdf164ce5aba253890e066129f134ca8d7e072ce5ad55c7" +
"21b9a13545ee04",
}}
func TestSweepTimeLockManual(t *testing.T) {
for _, tc := range sweepTimeLockManualCases {
// First, we need to parse the lock addr and make sure we can
// brute force the script with the information we have. If not,
// we can't continue anyway.
lockScript, err := lnd.GetP2WSHScript(
tc.timeLockAddr, &chaincfg.RegressionNetParams,
)
if err != nil {
t.Fatalf("invalid time lock addr: %v", err)
}
baseKey, err := hdkeychain.NewKeyFromString(tc.baseKey)
if err != nil {
t.Fatalf("couldn't derive base key: %v", err)
}
revPubKeyBytes, _ := hex.DecodeString(tc.remoteRevPubKey)
revPubKey, _ := btcec.ParsePubKey(revPubKeyBytes, btcec.S256())
_, _, _, _, _, err = tryKey(
baseKey, revPubKey, defaultCsvLimit, lockScript,
tc.keyIndex,
)
if err != nil {
t.Fatalf("couldn't derive key: %v", err)
}
}
}

@ -121,19 +121,36 @@ func PrivKeyFromPath(extendedKey *hdkeychain.ExtendedKey,
return privKey, nil
}
func ShaChainFromPath(extendedKey *hdkeychain.ExtendedKey,
path []uint32) (*shachain.RevocationProducer, error) {
func ShaChainFromPath(extendedKey *hdkeychain.ExtendedKey, path []uint32,
multiSigPubKey *btcec.PublicKey) (*shachain.RevocationProducer, error) {
privKey, err := PrivKeyFromPath(extendedKey, path)
if err != nil {
return nil, err
}
revRoot, err := chainhash.NewHash(privKey.Serialize())
// This is the legacy way where we just used the private key as the
// revocation root directly.
if multiSigPubKey == nil {
revRoot, err := chainhash.NewHash(privKey.Serialize())
if err != nil {
return nil, fmt.Errorf("could not create revocation "+
"root hash: %v", err)
}
return shachain.NewRevocationProducer(*revRoot), nil
}
// Perform an ECDH operation between the private key described in
// nextRevocationKeyDesc and our public multisig key. The result will be
// used to seed the revocation producer.
revRoot, err := ECDH(privKey, multiSigPubKey)
if err != nil {
return nil, fmt.Errorf("could not create revocation root "+
"hash: %v", err)
return nil, err
}
return shachain.NewRevocationProducer(*revRoot), nil
// Once we have the root, we can then generate our shachain producer
// and from that generate the per-commitment point.
return shachain.NewRevocationProducer(revRoot), nil
}
func IdentityPath(params *chaincfg.Params) string {

@ -1,6 +1,7 @@
package lnd
import (
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec"
@ -115,3 +116,21 @@ func maybeTweakPrivKey(signDesc *input.SignDescriptor,
}
return privKey
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// target private key and remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following
// operation:
//
// sx := k*P s := sha256(sx.SerializeCompressed())
func ECDH(privKey *btcec.PrivateKey, pub *btcec.PublicKey) ([32]byte, error) {
s := &btcec.PublicKey{}
x, y := btcec.S256().ScalarMult(pub.X, pub.Y, privKey.D.Bytes())
s.X = x
s.Y = y
h := sha256.Sum256(s.SerializeCompressed())
return h, nil
}

Loading…
Cancel
Save