diff --git a/engine/keys/type42/derivation.go b/engine/keys/type42/derivation.go new file mode 100644 index 000000000..77d0a68a7 --- /dev/null +++ b/engine/keys/type42/derivation.go @@ -0,0 +1,21 @@ +package type42 + +import ( + primitives "github.com/bitcoin-sv/go-sdk/primitives/ec" + "github.com/bitcoin-sv/spv-wallet/engine/spverrors" +) + +var ( + anyonePriv, _ = primitives.PrivateKeyFromBytes([]byte{1}) +) + +func derive(pubKey *primitives.PublicKey, derivationKey string) (*primitives.PublicKey, error) { + if pubKey == nil { + return nil, ErrDeriveKey.Wrap(spverrors.Newf("public key is nil")) + } + derivedPubByRef, err := pubKey.DeriveChild(anyonePriv, derivationKey) + if err != nil { + return nil, ErrDeriveKey.Wrap(err) + } + return derivedPubByRef, nil +} diff --git a/engine/keys/type42/destination.go b/engine/keys/type42/destination.go new file mode 100644 index 000000000..9af846d98 --- /dev/null +++ b/engine/keys/type42/destination.go @@ -0,0 +1,21 @@ +package type42 + +import ( + "fmt" + + primitives "github.com/bitcoin-sv/go-sdk/primitives/ec" + "github.com/bitcoin-sv/spv-wallet/engine/spverrors" +) + +// Destination derives a public key using a reference ID. +// It is intended to be used to derive a public key for paymail destinations. +func Destination(pubKey *primitives.PublicKey, referenceID string) (*primitives.PublicKey, error) { + if referenceID == "" { + return nil, ErrDeriveKey.Wrap(spverrors.Newf("reference ID is empty")) + } + derivedPubByRef, err := derive(pubKey, fmt.Sprintf("1-destination-%s", referenceID)) + if err != nil { + return nil, err + } + return derivedPubByRef, nil +} diff --git a/engine/keys/type42/destination_test.go b/engine/keys/type42/destination_test.go new file mode 100644 index 000000000..687965a14 --- /dev/null +++ b/engine/keys/type42/destination_test.go @@ -0,0 +1,46 @@ +package type42 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDestination(t *testing.T) { + t.Run("generate destination", func(t *testing.T) { + // given: + pubKey := makePubKey(t, "033014c226b8fe8260e21e75479a47a654e7b631b3bd13484d85c484f7791aa75b") + referenceID := "4c7bc22854691fda2d643f9c5cf6d218" + + // when: + destination, err := Destination(pubKey, referenceID) + + // then: + assert.NoError(t, err) + assert.Equal(t, "03d34d33cb9cf83ad5bea49c7ebb1adafe0c85ceda0e256a0c5db3b6cb28e3ec99", destination.ToDERHex()) + }) + + t.Run("try to generate destination on nil", func(t *testing.T) { + // given: + referenceID := "4c7bc22854691fda2d643f9c5cf6d218" + + // when: + destination, err := Destination(nil, referenceID) + + // then: + assert.ErrorIs(t, err, ErrDeriveKey) + assert.Nil(t, destination) + }) + + t.Run("try to generate destination on empty referenceID", func(t *testing.T) { + // given: + pubKey := makePubKey(t, "033014c226b8fe8260e21e75479a47a654e7b631b3bd13484d85c484f7791aa75b") + + // when: + destination, err := Destination(pubKey, "") + + // then: + assert.ErrorIs(t, err, ErrDeriveKey) + assert.Nil(t, destination) + }) +} diff --git a/engine/keys/type42/errors.go b/engine/keys/type42/errors.go new file mode 100644 index 000000000..3173c2c9c --- /dev/null +++ b/engine/keys/type42/errors.go @@ -0,0 +1,6 @@ +package type42 + +import "github.com/bitcoin-sv/spv-wallet/models" + +// ErrDeriveKey is an error that occurs when a child key cannot be derived from a public key. +var ErrDeriveKey = models.SPVError{Message: "Failed to derive a child key for provided public key", StatusCode: 500, Code: "error-derive-key"} diff --git a/engine/keys/type42/pki.go b/engine/keys/type42/pki.go new file mode 100644 index 000000000..d85321655 --- /dev/null +++ b/engine/keys/type42/pki.go @@ -0,0 +1,14 @@ +package type42 + +import primitives "github.com/bitcoin-sv/go-sdk/primitives/ec" + +const pkiDerivationKey = "1-pki-0" + +// PKI (Public Key Infrastructure) derives a public key using a constant derivation key. +func PKI(pubKey *primitives.PublicKey) (*primitives.PublicKey, error) { + derivedPubByRef, err := derive(pubKey, pkiDerivationKey) + if err != nil { + return nil, ErrDeriveKey.Wrap(err) + } + return derivedPubByRef, nil +} diff --git a/engine/keys/type42/pki_test.go b/engine/keys/type42/pki_test.go new file mode 100644 index 000000000..d3fe85a64 --- /dev/null +++ b/engine/keys/type42/pki_test.go @@ -0,0 +1,40 @@ +package type42 + +import ( + "testing" + + primitives "github.com/bitcoin-sv/go-sdk/primitives/ec" + "github.com/stretchr/testify/assert" +) + +func TestPKI(t *testing.T) { + t.Run("generate PKI", func(t *testing.T) { + // given: + pubKey := makePubKey(t, "033014c226b8fe8260e21e75479a47a654e7b631b3bd13484d85c484f7791aa75b") + + // when: + pki, err := PKI(pubKey) + + // then: + assert.NoError(t, err) + assert.Equal(t, "02b9a822f2db22649e14eedf75ba140cf5dacc6b2690cfae9da55b551069461705", pki.ToDERHex()) + }) + + t.Run("try to generate PKI on nil", func(t *testing.T) { + // when: + pki, err := PKI(nil) + + // then: + assert.ErrorIs(t, err, ErrDeriveKey) + assert.Nil(t, pki) + }) +} + +func makePubKey(t *testing.T, pubDERHex string) *primitives.PublicKey { + t.Helper() + pk, err := primitives.PublicKeyFromString(pubDERHex) + if err != nil { + t.Fatalf("failed to create public key: %s", err) + } + return pk +} diff --git a/engine/types/type42/linking_key.go b/engine/keys/type84/linking_key.go similarity index 99% rename from engine/types/type42/linking_key.go rename to engine/keys/type84/linking_key.go index c8a13ae74..040f0acd0 100644 --- a/engine/types/type42/linking_key.go +++ b/engine/keys/type84/linking_key.go @@ -1,4 +1,4 @@ -package type42 +package type84 import ( "crypto/hmac" diff --git a/engine/types/type42/linking_key_test.go b/engine/keys/type84/linking_key_test.go similarity index 99% rename from engine/types/type42/linking_key_test.go rename to engine/keys/type84/linking_key_test.go index 2524dd536..baf707954 100644 --- a/engine/types/type42/linking_key_test.go +++ b/engine/keys/type84/linking_key_test.go @@ -1,4 +1,4 @@ -package type42 +package type84 import ( "encoding/hex" diff --git a/engine/pike/pike.go b/engine/pike/pike.go index 0500aee4f..c46070398 100644 --- a/engine/pike/pike.go +++ b/engine/pike/pike.go @@ -19,9 +19,9 @@ import ( ec "github.com/bitcoin-sv/go-sdk/primitives/ec" "github.com/bitcoin-sv/go-sdk/script" + "github.com/bitcoin-sv/spv-wallet/engine/keys/type84" "github.com/bitcoin-sv/spv-wallet/engine/script/template" "github.com/bitcoin-sv/spv-wallet/engine/spverrors" - "github.com/bitcoin-sv/spv-wallet/engine/types/type42" ) // GenerateOutputsTemplate creates a Pike output template @@ -43,7 +43,7 @@ func GenerateLockingScriptsFromTemplates(outputsTemplate []*template.OutputTempl return nil, spverrors.Wrapf(err, "error creating script from hex string") } - dPK, err := type42.DeriveLinkedKey(senderPubKey, receiverPubKey, fmt.Sprintf("%s-%d", reference, idx)) + dPK, err := type84.DeriveLinkedKey(senderPubKey, receiverPubKey, fmt.Sprintf("%s-%d", reference, idx)) if err != nil { return nil, spverrors.Wrapf(err, "error deriving linked key") }