diff --git a/config/pkg/pldconf/signer.go b/config/pkg/pldconf/signer.go index 11203512a..a262f8c48 100644 --- a/config/pkg/pldconf/signer.go +++ b/config/pkg/pldconf/signer.go @@ -56,7 +56,8 @@ type ConfigKeyPathEntry struct { Index uint64 `json:"index"` } -type SigningKeyConfigEntry struct { +type StaticKeyReference struct { + KeyHandle string `json:"keyHandle,omitempty"` // causes resolution to be bypassed, similarly to if a key-mapping already exists in the DB for runtime resolution Name string `json:"name"` Index uint64 `json:"index"` Attributes map[string]string `json:"attributes"` @@ -64,17 +65,17 @@ type SigningKeyConfigEntry struct { } type KeyDerivationConfig struct { - Type KeyDerivationType `json:"type"` - SeedKeyPath SigningKeyConfigEntry `json:"seedKey"` - BIP44DirectResolution bool `json:"bip44DirectResolution"` - BIP44Prefix *string `json:"bip44Prefix"` - BIP44HardenedSegments *int `json:"bip44HardenedSegments"` + Type KeyDerivationType `json:"type"` + SeedKeyPath StaticKeyReference `json:"seedKey"` + BIP44DirectResolution bool `json:"bip44DirectResolution"` + BIP44Prefix *string `json:"bip44Prefix"` + BIP44HardenedSegments *int `json:"bip44HardenedSegments"` } var KeyDerivationDefaults = &KeyDerivationConfig{ BIP44Prefix: confutil.P("m/44'/60'"), BIP44HardenedSegments: confutil.P(1), // in addition to the prefix, so `m/44'/60'/0'/0/0` for example with 3 segments, on top of the prefix - SeedKeyPath: SigningKeyConfigEntry{Name: "seed", Index: 0}, + SeedKeyPath: StaticKeyReference{Name: "seed", Index: 0}, } type StaticKeyEntryEncoding string diff --git a/operator/internal/controller/paladin_controller.go b/operator/internal/controller/paladin_controller.go index 9cacc2eb4..e72de5653 100644 --- a/operator/internal/controller/paladin_controller.go +++ b/operator/internal/controller/paladin_controller.go @@ -859,7 +859,7 @@ func (r *PaladinReconciler) generatePaladinSigners(ctx context.Context, node *co // Upsert a secret if we've been asked to. We use a mnemonic in this case (rather than directly generating a 32byte seed) if s.Type == corev1alpha1.SignerType_AutoHDWallet { wallet.Signer.KeyDerivation.Type = pldconf.KeyDerivationTypeBIP32 - wallet.Signer.KeyDerivation.SeedKeyPath = pldconf.SigningKeyConfigEntry{Name: "seed"} + wallet.Signer.KeyDerivation.SeedKeyPath = pldconf.StaticKeyReference{Name: "seed"} if err := r.generateBIP39SeedSecretIfNotExist(ctx, node, s.Secret); err != nil { return err } diff --git a/toolkit/go/pkg/signer/hd_key_derivation.go b/toolkit/go/pkg/signer/hd_key_derivation.go index 9373c47d7..8fc2282bd 100644 --- a/toolkit/go/pkg/signer/hd_key_derivation.go +++ b/toolkit/go/pkg/signer/hd_key_derivation.go @@ -41,7 +41,10 @@ type hdWalletPathEntry struct { Index uint64 } -func configToKeyResolutionRequest(k *pldconf.SigningKeyConfigEntry) *signerapi.ResolveKeyRequest { +func configToKeyResolutionRequest(k *pldconf.StaticKeyReference) (string, *signerapi.ResolveKeyRequest) { + if k.KeyHandle != "" { + return k.KeyHandle, nil + } keyReq := &signerapi.ResolveKeyRequest{ Name: k.Name, Index: k.Index, @@ -54,7 +57,7 @@ func configToKeyResolutionRequest(k *pldconf.SigningKeyConfigEntry) *signerapi.R Index: p.Index, }) } - return keyReq + return "", keyReq } func (sm *signingModule[C]) initHDWallet(ctx context.Context, conf *pldconf.KeyDerivationConfig) (err error) { @@ -67,11 +70,19 @@ func (sm *signingModule[C]) initHDWallet(ctx context.Context, conf *pldconf.KeyD bip44HardenedSegments: confutil.IntMin(conf.BIP44HardenedSegments, 0, *pldconf.KeyDerivationDefaults.BIP44HardenedSegments), } seedKeyPath := pldconf.KeyDerivationDefaults.SeedKeyPath - if conf.SeedKeyPath.Name != "" { + if conf.SeedKeyPath.Name != "" || conf.SeedKeyPath.KeyHandle != "" || len(conf.SeedKeyPath.Attributes) > 0 { seedKeyPath = conf.SeedKeyPath } // Note we don't have any way to store the resolved keyHandle, so we resolve it every time we start - seed, _, err := sm.keyStore.FindOrCreateLoadableKey(ctx, configToKeyResolutionRequest(&seedKeyPath), sm.new32ByteRandomSeed) + var seed []byte + keyHandle, seedResolve := configToKeyResolutionRequest(&seedKeyPath) + if keyHandle != "" { + // We have been provided a pre-resolved key handle + seed, err = sm.keyStore.LoadKeyMaterial(ctx, keyHandle) + } else { + // We need to call resolve to resolve the key material + seed, _, err = sm.keyStore.FindOrCreateLoadableKey(ctx, seedResolve, sm.new32ByteRandomSeed) + } if err != nil { return err } diff --git a/toolkit/go/pkg/signer/hd_key_derivation_test.go b/toolkit/go/pkg/signer/hd_key_derivation_test.go index 642f4f12f..adb008f75 100644 --- a/toolkit/go/pkg/signer/hd_key_derivation_test.go +++ b/toolkit/go/pkg/signer/hd_key_derivation_test.go @@ -78,6 +78,49 @@ func TestHDSigningStaticExample(t *testing.T) { } +func TestHDSigningStaticExamplePreResolved(t *testing.T) { + + ctx := context.Background() + sm, err := NewSigningModule(ctx, &signerapi.ConfigNoExt{ + KeyDerivation: pldconf.KeyDerivationConfig{ + Type: pldconf.KeyDerivationTypeBIP32, + SeedKeyPath: pldconf.StaticKeyReference{ + KeyHandle: "directly.resolved", + }, + }, + KeyStore: pldconf.KeyStoreConfig{ + Type: pldconf.KeyStoreTypeStatic, + Static: pldconf.StaticKeyStoreConfig{ + Keys: map[string]pldconf.StaticKeyEntryConfig{ + "directly.resolved": { + Encoding: "hex", + Inline: tktypes.RandHex(32), + }, + }, + }, + }, + }) + require.NoError(t, err) + + res, err := sm.Resolve(ctx, &signerapi.ResolveKeyRequest{ + RequiredIdentifiers: []*signerapi.PublicKeyIdentifierType{{Algorithm: algorithms.ECDSA_SECP256K1, VerifierType: verifiers.ETH_ADDRESS}}, + Name: "key1", + Index: 0, + }) + require.NoError(t, err) + assert.Equal(t, "m/44'/60'/0'", res.KeyHandle) + + resSign, err := sm.Sign(ctx, &signerapi.SignRequest{ + KeyHandle: res.KeyHandle, + Algorithm: algorithms.ECDSA_SECP256K1, + PayloadType: signpayloads.OPAQUE_TO_RSV, + Payload: ([]byte)("some data"), + }) + require.NoError(t, err) + assert.NotEmpty(t, resSign.Payload) + +} + func TestHDSigningDirectResNoPrefix(t *testing.T) { ctx := context.Background() @@ -157,7 +200,7 @@ func TestHDSigningDefaultBehaviorOK(t *testing.T) { sm, err := NewSigningModule(ctx, &signerapi.ConfigNoExt{ KeyDerivation: pldconf.KeyDerivationConfig{ Type: pldconf.KeyDerivationTypeBIP32, - SeedKeyPath: pldconf.SigningKeyConfigEntry{ + SeedKeyPath: pldconf.StaticKeyReference{ Name: "seed", Index: 0, Path: []pldconf.ConfigKeyPathEntry{ @@ -292,7 +335,7 @@ func TestHDInitBadSeed(t *testing.T) { _, err = NewSigningModule(ctx, &signerapi.ConfigNoExt{ KeyDerivation: pldconf.KeyDerivationConfig{ Type: pldconf.KeyDerivationTypeBIP32, - SeedKeyPath: pldconf.SigningKeyConfigEntry{ + SeedKeyPath: pldconf.StaticKeyReference{ Name: "missing", }, }, @@ -319,7 +362,7 @@ func TestHDInitGenSeed(t *testing.T) { sm, err := NewSigningModule(ctx, &signerapi.ConfigNoExt{ KeyDerivation: pldconf.KeyDerivationConfig{ Type: pldconf.KeyDerivationTypeBIP32, - SeedKeyPath: pldconf.SigningKeyConfigEntry{ + SeedKeyPath: pldconf.StaticKeyReference{ Name: "seed", Path: []pldconf.ConfigKeyPathEntry{{Name: "generate"}}, }, diff --git a/toolkit/go/pkg/signerapi/keystore.go b/toolkit/go/pkg/signerapi/keystore.go index 9275705cf..35e3d4452 100644 --- a/toolkit/go/pkg/signerapi/keystore.go +++ b/toolkit/go/pkg/signerapi/keystore.go @@ -24,9 +24,9 @@ type KeyStoreFactory[C ExtensibleConfig] interface { } // All cryptographic storage needs to support master key encryption, by which the bytes -// can be decrypted an loaded into volatile memory for use, and then discarded. +// can be decrypted and loaded into volatile memory for use, and then discarded. // -// The implementation is not required to know how to generate or validate such data, just now +// The implementation is not required to know how to generate or validate such data, just how // to securely store and retrieve it using only the information contained in the returned // keyHandle. If the implementation finds it does not exist, it can invoke the callback function to generate // a new suitable random string to encrypt and store.