From 632b1e4e09591a76897701001d83658d31c3d26b Mon Sep 17 00:00:00 2001 From: Alec Chen <30279834+alecchendev@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:53:23 -0800 Subject: [PATCH] Add a helper method to derive an L1 wallet xpub (#156) When a user first enables an L1 wallet for their node, they'll need to provide an extended public key from which we can derive addresses to send funds to. This adds a helper function to provide an example/make it easy for the user to do this. --- remotesigning/test/validation_test.go | 44 +++++++++++++++++++++++++++ remotesigning/validation.go | 31 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/remotesigning/test/validation_test.go b/remotesigning/test/validation_test.go index 728e689..cd19455 100644 --- a/remotesigning/test/validation_test.go +++ b/remotesigning/test/validation_test.go @@ -88,6 +88,50 @@ func TestDerivationPath(t *testing.T) { } } +func TestL1WalletDerivationPath(t *testing.T) { + masterSeed, err := hex.DecodeString("69f580170954f411bbabc60118c0a3a0e483381d196d4087d32b78bdfee4a114") + assert.NoError(t, err) + + tests := []struct { + name string + network chaincfg.Params + expectedPath string + expectedKey string + expectedErrMsg string + }{ + { + name: "mainnet", + network: chaincfg.MainNetParams, + expectedPath: "m/84'/0'/0'", + expectedKey: "xpub6D87MELKGzkxrhQcQVNddwHLC3td1ftP4sS6zBaz5JU4MimSwKqK3XcEnrE38mgfydBmsedXc35tETx5HALSzBfre3ZXo26nS2kBmPwvJ3n", + }, + { + name: "testnet", + network: chaincfg.TestNet3Params, + expectedPath: "m/84'/1'/0'", + expectedKey: "tpubDDPLaqgr8bawQ6chGQ7ZkSChHBjVihms7dXdcZF8XvDNirxsctoK17brjJt7eMb7ZgppHz86uQNT1ksw89svDiAqNeMKsHfYfs8K8F1kq8m", + }, + { + name: "regtest", + network: chaincfg.RegressionNetParams, + expectedPath: "m/84'/2'/0'", + expectedKey: "tpubDC7fZywQZQ45q3ebc3HC2CiCWUe1p3g5ZrM4uh7GBXRqnBzpG5qLD8swyYqUThvmNksGLEHYcjtChXUXGUWXuH1FqTF6rwwr2ErEoZJQnE3", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path, err := remotesigning.L1WalletDerivationPrefix(&tt.network) + assert.NoError(t, err) + assert.Equal(t, tt.expectedPath, path) + + key, err := remotesigning.DeriveL1WalletHardenedXpub(masterSeed, &tt.network) + assert.NoError(t, err) + assert.Equal(t, tt.expectedKey, key.String()) + }) + } +} + func TestValidTransaction(t *testing.T) { tests := []struct { name string diff --git a/remotesigning/validation.go b/remotesigning/validation.go index ddad160..c5fcdc8 100644 --- a/remotesigning/validation.go +++ b/remotesigning/validation.go @@ -190,3 +190,34 @@ func GenerateP2WPKHFromPubkey(child_pubkey []byte) ([]byte, error) { AddData(pkHash). Script() } + +func L1WalletDerivationPrefix(networkParams *chaincfg.Params) (string, error) { + var network uint32 + switch networkParams.Name { + case chaincfg.MainNetParams.Name: + network = 0 + case chaincfg.TestNet3Params.Name: + network = 1 + case chaincfg.RegressionNetParams.Name: + network = 2 + default: + return "", fmt.Errorf("unsupported network") + } + return fmt.Sprintf("m/84'/%d'/0'", network), nil +} + +func DeriveL1WalletHardenedXpub(masterSeed []byte, networkParams *chaincfg.Params) (*hdkeychain.ExtendedKey, error) { + derivationPathString, err := L1WalletDerivationPrefix(networkParams) + if err != nil { + return nil, err + } + derivationPath, err := DerivationPathFromString(derivationPathString) + if err != nil { + return nil, err + } + key, err := DeriveKey(masterSeed, derivationPath, networkParams) + if err != nil { + return nil, err + } + return key.Neuter() +}