You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@milagro.apache.org by cm...@apache.org on 2019/09/03 08:58:15 UTC

[incubator-milagro-dta] 01/01: Qredo Plugin

This is an automated email from the ASF dual-hosted git repository.

cmorris pushed a commit to branch qredo-plugin
in repository https://gitbox.apache.org/repos/asf/incubator-milagro-dta.git

commit c8dab8c507da4d5de7e2e51046c0ff9f7bd32d1d
Author: Christopher Morris <ch...@morris.net>
AuthorDate: Wed Aug 28 14:43:28 2019 +0100

    Qredo Plugin
---
 pkg/qredoplugin/coin.go         | 176 +++++++++++++++++++++++++++++++
 pkg/qredoplugin/coin_test.go    |  52 ++++++++++
 pkg/qredoplugin/helpers.go      | 225 ++++++++++++++++++++++++++++++++++++++++
 pkg/qredoplugin/helpers_test.go |  47 +++++++++
 pkg/qredoplugin/service.go      | 103 ++++++++++++++++++
 plugins/qredoplugin.go          |   9 ++
 6 files changed, 612 insertions(+)

diff --git a/pkg/qredoplugin/coin.go b/pkg/qredoplugin/coin.go
new file mode 100644
index 0000000..c2a6cfa
--- /dev/null
+++ b/pkg/qredoplugin/coin.go
@@ -0,0 +1,176 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package qredoplugin
+
+/*
+The coin constans are defined here
+https://github.com/satoshilabs/slips/blob/master/slip-0044.md
+https://www.thepolyglotdeveloper.com/2018/02/generate-cryptocurrency-private-keys-public-addresses-golang/
+https://godoc.org/bitbucket.org/dchapes/ripple/crypto/rkey#example-package--Address
+
+*/
+
+import (
+	"encoding/hex"
+	"fmt"
+	"strings"
+
+	"github.com/btcsuite/btcd/btcec"
+	"github.com/btcsuite/btcd/chaincfg"
+	"github.com/btcsuite/btcutil"
+	ethcrypto "github.com/ethereum/go-ethereum/crypto"
+	"github.com/pkg/errors"
+)
+
+var (
+	errUnsupportedCoin = errors.New("unsupported coin")
+)
+
+//Network list of Cryptocurrency networks
+type Network struct {
+	name        string
+	symbol      string
+	xpubkey     byte
+	xprivatekey byte
+}
+
+var network = map[string]Network{
+	"rdd":     {name: "reddcoin", symbol: "rdd", xpubkey: 0x3d, xprivatekey: 0xbd},
+	"dgb":     {name: "digibyte", symbol: "dgb", xpubkey: 0x1e, xprivatekey: 0x80},
+	"doge":    {name: "dogecoin", symbol: "doge", xpubkey: 0x1e, xprivatekey: 0x9e},
+	"via":     {name: "viacoin", symbol: "via", xpubkey: 0x47, xprivatekey: 0xc7},
+	"btc":     {name: "bitcoin", symbol: "btc", xpubkey: 0x00, xprivatekey: 0x80},
+	"testbtc": {name: "bitcoin testnet", symbol: "btc", xpubkey: 0x6f, xprivatekey: 0xef},
+	"ltc":     {name: "litecoin", symbol: "ltc", xpubkey: 0x30, xprivatekey: 0xb0},
+}
+
+func addressForPublicKey(publicKey string, coinType int) (string, error) {
+	pubK, _ := hex.DecodeString(publicKey)
+	pubKeyBytes := []byte(pubK)
+
+	switch coinType {
+	case 0: //Bitcoin
+		return network["btc"].pubkeyToAddress(pubKeyBytes, false)
+	case 1: //Bitcoin Testnet
+		return network["testbtc"].pubkeyToAddress(pubKeyBytes, false)
+	case 2: //litecoin
+		return network["ltc"].pubkeyToAddress(pubKeyBytes, false)
+	case 3: //Dogecoin
+		return network["doge"].pubkeyToAddress(pubKeyBytes, false)
+	case 60: //Ethereum
+		compByte, err := hex.DecodeString(publicKey)
+		pubKey, err := btcec.ParsePubKey([]byte(compByte), btcec.S256())
+		if err != nil {
+			return "", errors.Wrap(err, "Failed to generate Ethereum Address")
+		}
+		pubECDSA := pubKey.ToECDSA()
+		address1 := ethcrypto.PubkeyToAddress(*pubECDSA)
+		retAddress := fmt.Sprintf("%x", address1)
+		return retAddress, nil
+		// case 128: //Monero
+		// case 144: //Ripple
+		// case 145: //Bitcoin Cash
+		// case 148: //Stellar
+		// case 1815: //Cardano
+	}
+	return "", errUnsupportedCoin
+}
+
+func addressForPrivateKey(privateKey []byte, coinType int) (string, error) {
+	switch coinType {
+	case 0: //Bitcoin
+		return network["btc"].privkeyToAddress(privateKey, false)
+	case 1: //Bitcoin Testnet
+		return network["testbtc"].privkeyToAddress(privateKey, false)
+	case 2: //litecoin
+		return network["ltc"].privkeyToAddress(privateKey, false)
+	case 3: //Dogecoin
+		return network["doge"].privkeyToAddress(privateKey, false)
+	case 60: //Ethereum
+		_, cpub1 := btcec.PrivKeyFromBytes(btcec.S256(), privateKey)
+		pubKey := cpub1.SerializeUncompressed()
+		address := ethcrypto.Keccak256(pubKey[1:])[12:]
+		addRet := strings.ToLower(fmt.Sprintf("%x", address))
+		return addRet, nil
+		// case 128: //Monero
+		// case 144: //Ripple
+		// case 145: //Bitcoin Cash
+		// case 148: //Stellar
+		// case 1815: //Cardano
+	}
+	return "", errUnsupportedCoin
+}
+
+func (network Network) privkeyToAddress(privateKey []byte, compressed bool) (string, error) {
+	wif, _ := network.createPrivateKeyFromBytes(privateKey)
+	address, _ := network.getAddress(wif)
+	if compressed == true {
+		address.SetFormat(btcutil.PKFCompressed)
+	} else {
+		address.SetFormat(btcutil.PKFUncompressed)
+	}
+	return address.EncodeAddress(), nil
+}
+
+func (network Network) pubkeyToAddress(publicKey []byte, compressed bool) (string, error) {
+	mainNetAddr, err := btcutil.NewAddressPubKey(publicKey, network.getNetworkParams())
+	if err != nil {
+		return "", errors.Wrap(err, "Failed to decode Public Key")
+	}
+	if compressed == true {
+		mainNetAddr.SetFormat(btcutil.PKFCompressed)
+	} else {
+		mainNetAddr.SetFormat(btcutil.PKFUncompressed)
+	}
+	return mainNetAddr.EncodeAddress(), nil
+}
+
+func (network Network) getNetworkParams() *chaincfg.Params {
+	networkParams := &chaincfg.MainNetParams
+	networkParams.PubKeyHashAddrID = network.xpubkey
+	networkParams.PrivateKeyID = network.xprivatekey
+	return networkParams
+}
+
+func (network Network) createPrivateKeyFromBytes(privateKey []byte) (*btcutil.WIF, error) {
+	secret, _ := btcec.PrivKeyFromBytes(btcec.S256(), privateKey)
+	return btcutil.NewWIF(secret, network.getNetworkParams(), true)
+}
+
+func (network Network) createPrivateKey() (*btcutil.WIF, error) {
+	secret, err := btcec.NewPrivateKey(btcec.S256())
+	if err != nil {
+		return nil, err
+	}
+	return btcutil.NewWIF(secret, network.getNetworkParams(), true)
+}
+
+func (network Network) importWIF(wifStr string) (*btcutil.WIF, error) {
+	wif, err := btcutil.DecodeWIF(wifStr)
+	if err != nil {
+		return nil, err
+	}
+	if !wif.IsForNet(network.getNetworkParams()) {
+		return nil, errors.New("The WIF string is not valid for the `" + network.name + "` network")
+	}
+	return wif, nil
+}
+
+func (network Network) getAddress(wif *btcutil.WIF) (*btcutil.AddressPubKey, error) {
+	return btcutil.NewAddressPubKey(wif.PrivKey.PubKey().SerializeUncompressed(), network.getNetworkParams())
+}
diff --git a/pkg/qredoplugin/coin_test.go b/pkg/qredoplugin/coin_test.go
new file mode 100644
index 0000000..9d9cff6
--- /dev/null
+++ b/pkg/qredoplugin/coin_test.go
@@ -0,0 +1,52 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package qredoplugin
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_AddressForPublicKey(t *testing.T) {
+	pubKey := "0487DBF8D88A860270AB7D689EB44C2DFFF768D2F7851A753FACF356978B82CE4ACB5C9B061FC884668D9BB46B83D6BF180A2099F397142785D2E03DACCEF03D01"
+	btcAddress, err := addressForPublicKey(pubKey, 0)
+	assert.Nil(t, err, "Error should be nil")
+	assert.Equal(t, "1MwiNcg3v19BLeawNJKL8L18m4Tzmtua5T", btcAddress)
+
+	btcTestNetAddress, err := addressForPublicKey(pubKey, 1)
+	assert.Nil(t, err, "Error should be nil")
+	assert.Equal(t, "n2Tfffm2j2aS7m4Z5sHhxFDTd44hfnDhPz", btcTestNetAddress)
+
+	_, err = addressForPublicKey(pubKey, 9999999999)
+	assert.EqualError(t, err, "unsupported coin")
+}
+func Test_AddressForPrivateKey(t *testing.T) {
+	privKey, _ := hex.DecodeString("EB354D4B18E0B4AC6E63369F33D0CFFE7F3C09101D29678877A6CE8879D7E152")
+	btcAddress, err := addressForPrivateKey(privKey, 0)
+	assert.Nil(t, err, "Error should be nil")
+	assert.Equal(t, "1MwiNcg3v19BLeawNJKL8L18m4Tzmtua5T", btcAddress)
+
+	btcTestNetAddress, err := addressForPrivateKey(privKey, 1)
+	assert.Nil(t, err, "Error should be nil")
+	assert.Equal(t, "n2Tfffm2j2aS7m4Z5sHhxFDTd44hfnDhPz", btcTestNetAddress)
+
+	_, err = addressForPrivateKey(privKey, 9999999999)
+	assert.EqualError(t, err, "unsupported coin")
+}
diff --git a/pkg/qredoplugin/helpers.go b/pkg/qredoplugin/helpers.go
new file mode 100644
index 0000000..9295106
--- /dev/null
+++ b/pkg/qredoplugin/helpers.go
@@ -0,0 +1,225 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package qredoplugin
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+	"math/big"
+
+	"github.com/apache/incubator-milagro-dta/libs/cryptowallet"
+	"github.com/apache/incubator-milagro-dta/libs/documents"
+	"github.com/apache/incubator-milagro-dta/pkg/common"
+	"github.com/btcsuite/btcd/btcec"
+	"github.com/pkg/errors"
+)
+
+func deriveFinalPrivateKey(s *Service, order documents.OrderDoc, beneficiariesSikeSK []byte, beneficiariesSeed []byte, beneficiaryIDDocumentCID string, nodeID string, signingBlsPK []byte) (string, error) {
+	if beneficiaryIDDocumentCID != "" {
+		//we are using the beneficiary specified in order Part 3
+		beneficiaryBlob := order.OrderPart3.BeneficiaryEncryptedData
+
+		//Decrypt the Envelope intented for the Beneficiary
+		privateKeyPart1of1, err := adhocEncryptedEnvelopeDecode(s, beneficiariesSikeSK, beneficiaryBlob, beneficiaryIDDocumentCID, signingBlsPK)
+		if err != nil {
+			return "", err
+		}
+		//Calculate the final private key by Eliptical Key addition of both parts
+		privateKeyPart2of2 := order.OrderDocument.OrderPart4.Secret
+
+		finalPrivateKey, err := addPrivateKeys(privateKeyPart1of1, privateKeyPart2of2)
+
+		if err != nil {
+			return "", err
+		}
+		return finalPrivateKey, err
+	}
+
+	//we are using the beneficiary specified in the order part 1
+	privateKeyPart2of2 := order.OrderDocument.OrderPart4.Secret
+	if order.OrderDocument.BeneficiaryCID != nodeID {
+		//need to forward this data to the beneficiary to complete redemption
+		return "", errors.New("Currently beneficiary must be the same as the Principal")
+	}
+	//restore the Seed
+	_, _, ecAddPrivateKey, err := cryptowallet.Bip44Address(beneficiariesSeed, cryptowallet.CoinTypeBitcoinMain, 0, 0, 0)
+	if err != nil {
+		return "", err
+	}
+	privateKeyPart1of1 := hex.EncodeToString(ecAddPrivateKey.Serialize())
+	finalPrivateKey, err := addPrivateKeys(privateKeyPart1of1, privateKeyPart2of2)
+	if err != nil {
+		return "", err
+	}
+	return finalPrivateKey, err
+
+}
+
+func adhocEncryptedEnvelopeEncode(s *Service, nodeID string, beneficiaryIDDocumentCID string, order documents.OrderDoc, blsSK []byte) ([]byte, error) {
+	//Regenerate the original Princaipal Priv Key based on Order
+	if beneficiaryIDDocumentCID == "" {
+		//beneficiaryIDDocumentCID is empty when it was passed in the Inital Deposit Order
+		return nil, nil
+	}
+	seedHex, err := common.RetrieveSeed(s.Store, order.Reference)
+	if err != nil {
+		return nil, err
+	}
+	seedOrderModifier := order.OrderDocument.OrderPart2.PreviousOrderCID
+	seed, err := hex.DecodeString(seedHex)
+	if err != nil {
+		return nil, err
+	}
+	concatenatedSeeds := append(seed, seedOrderModifier...)
+	finalSeed := sha256.Sum256(concatenatedSeeds)
+	finalSeedHex := hex.EncodeToString(finalSeed[:])
+	privateKeyPart1of2, err := cryptowallet.RedeemSecret(finalSeedHex)
+	if err != nil {
+		return nil, err
+	}
+	beneficiaryIDDocument, err := common.RetrieveIDDocFromIPFS(s.Ipfs, beneficiaryIDDocumentCID)
+	if err != nil {
+		return nil, err
+	}
+	secretBody := &documents.SimpleString{Content: privateKeyPart1of2}
+	header := &documents.Header{}
+	recipients := map[string]documents.IDDoc{
+		beneficiaryIDDocumentCID: beneficiaryIDDocument,
+	}
+	docEnv, err := documents.Encode(nodeID, nil, secretBody, header, blsSK, recipients)
+	if err != nil {
+		return nil, err
+	}
+	return docEnv, err
+}
+
+func adhocEncryptedEnvelopeDecode(s *Service, sikeSK []byte, beneficiaryBlob []byte, beneficiaryIDDocumentCID string, signingBlsPK []byte) (string, error) {
+	//Regenerate the original Principal Priv Key based on Order
+	secretBody := &documents.SimpleString{}
+	_, err := documents.Decode(beneficiaryBlob, "INTERNAL", sikeSK, beneficiaryIDDocumentCID, nil, secretBody, signingBlsPK)
+	if err != nil {
+		return "", err
+	}
+	return secretBody.Content, err
+}
+
+func generateFinalPubKey(s *Service, pubKeyPart2of2 string, order documents.OrderDoc) (string, string, error) {
+	beneficiaryIDDocumentCID := order.OrderDocument.BeneficiaryCID
+	coinType := order.Coin
+	var pubKeyPart1of2 string
+
+	if beneficiaryIDDocumentCID == "" {
+		//There is no beneficiary ID so we do it all locally based on
+		//Retrieve the Local Seed
+		seedHex, err := common.RetrieveSeed(s.Store, order.Reference)
+		if err != nil {
+			return "", "", err
+		}
+		seedOrderModifier := order.OrderDocument.OrderPart2.PreviousOrderCID
+		seed, err := hex.DecodeString(seedHex)
+		if err != nil {
+			return "", "", err
+		}
+		concatenatedSeeds := append(seed, seedOrderModifier...)
+		finalSeed := sha256.Sum256(concatenatedSeeds)
+		finalSeedHex := hex.EncodeToString(finalSeed[:])
+		//Use HD Wallet to obtain the local Public Key
+		pubKeyPart1of2, err = cryptowallet.RedeemPublicKey(finalSeedHex)
+
+		if err != nil {
+			return "", "", err
+		}
+	}
+
+	if beneficiaryIDDocumentCID != "" {
+		//There is a BeneficiaryID use it to generate the key
+		//Get beneficiary's identity out of IPFS
+		id := &documents.IDDoc{}
+		rawDocI, err := s.Ipfs.Get(beneficiaryIDDocumentCID)
+		if err != nil {
+			return "", "", errors.Wrapf(err, "Read identity Doc")
+		}
+		err = documents.DecodeIDDocument(rawDocI, beneficiaryIDDocumentCID, id)
+		if err != nil {
+			return "", "", err
+		}
+		pubKeyPart1of2 = hex.EncodeToString(id.BeneficiaryECPublicKey)
+	}
+
+	finalPublicKey, err := addPublicKeys(pubKeyPart2of2, pubKeyPart1of2)
+
+	if err != nil {
+		return "", "", err
+	}
+	addressForPublicKey, err := addressForPublicKey(finalPublicKey, int(coinType))
+	if err != nil {
+		return "", "", err
+	}
+	return finalPublicKey, addressForPublicKey, nil
+
+}
+
+//AddPrivateKeys Perform eliptical key additon on 2 privates keys
+func addPrivateKeys(key1 string, key2 string) (string, error) {
+	curveOrder := new(big.Int)
+	curveOrder.SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
+	priv1 := new(big.Int)
+	priv2 := new(big.Int)
+	priv3 := new(big.Int)
+	priv1.SetString(key1, 16)
+	priv2.SetString(key2, 16)
+	priv3.Add(priv1, priv2)
+	if curveOrder.Cmp(priv3) == -1 {
+		priv3.Sub(priv3, curveOrder)
+	}
+	priv4 := fmt.Sprintf("%064x", priv3)
+	return priv4, nil
+}
+
+//AddPublicKeys Perform eliptical key addition on 2 public keys
+func addPublicKeys(key1 string, key2 string) (string, error) {
+	pub1Hex, err := hex.DecodeString(key1)
+	if err != nil {
+		return "", errors.Wrap(err, "Failed to hex decode String")
+	}
+	dpub1, err := btcec.ParsePubKey(pub1Hex, btcec.S256())
+	if err != nil {
+		return "", errors.Wrap(err, "Failed to Parse Public Key")
+	}
+	pub2Hex, err := hex.DecodeString(key2)
+	if err != nil {
+		return "", errors.Wrap(err, "Failed to hex decode String")
+	}
+	dpub2, err := btcec.ParsePubKey(pub2Hex, btcec.S256())
+	if err != nil {
+		return "", errors.Wrap(err, "Failed to Parse Public Key")
+	}
+	x, y := btcec.S256().Add(dpub1.X, dpub1.Y, dpub2.X, dpub2.Y)
+	comp := fmt.Sprintf("04%064X%064X", x, y)
+	compByte, err := hex.DecodeString(comp)
+	if err != nil {
+		return "", err
+	}
+	pubKey, err := btcec.ParsePubKey([]byte(compByte), btcec.S256())
+	if err != nil {
+		return "", err
+	}
+	pubKeyString := hex.EncodeToString(pubKey.SerializeCompressed())
+	return pubKeyString, nil
+}
diff --git a/pkg/qredoplugin/helpers_test.go b/pkg/qredoplugin/helpers_test.go
new file mode 100644
index 0000000..857e2fa
--- /dev/null
+++ b/pkg/qredoplugin/helpers_test.go
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package qredoplugin
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/btcsuite/btcd/btcec"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_AddKeys(t *testing.T) {
+	privKey1, _ := btcec.NewPrivateKey(btcec.S256())
+	privKey2, _ := btcec.NewPrivateKey(btcec.S256())
+	privKey1Str := hex.EncodeToString(privKey1.Serialize())
+	privKey2Str := hex.EncodeToString(privKey2.Serialize())
+
+	pubKey1 := btcec.PublicKey(privKey1.ToECDSA().PublicKey)
+	pubKey2 := btcec.PublicKey(privKey2.ToECDSA().PublicKey)
+	pubKey1Str := hex.EncodeToString(pubKey1.SerializeUncompressed())
+	pubKey2Str := hex.EncodeToString(pubKey2.SerializeUncompressed())
+
+	//run Additions
+	privAdd, _ := addPrivateKeys(privKey1Str, privKey2Str)
+	pubAdd, _ := addPublicKeys(pubKey1Str, pubKey2Str)
+
+	privAddBytes, _ := hex.DecodeString(privAdd)
+	_, pubKeyFromPrivate := btcec.PrivKeyFromBytes(btcec.S256(), privAddBytes)
+	pubKeyFromPrivateString := hex.EncodeToString(pubKeyFromPrivate.SerializeCompressed())
+	assert.Equal(t, pubKeyFromPrivateString, pubAdd, "Addition failed")
+}
diff --git a/pkg/qredoplugin/service.go b/pkg/qredoplugin/service.go
new file mode 100644
index 0000000..855b2fe
--- /dev/null
+++ b/pkg/qredoplugin/service.go
@@ -0,0 +1,103 @@
+package qredoplugin
+
+import (
+	"strconv"
+
+	"github.com/apache/incubator-milagro-dta/libs/cryptowallet"
+	"github.com/apache/incubator-milagro-dta/libs/documents"
+	"github.com/apache/incubator-milagro-dta/pkg/api"
+	"github.com/apache/incubator-milagro-dta/pkg/common"
+	"github.com/apache/incubator-milagro-dta/pkg/defaultservice"
+
+	"github.com/pkg/errors"
+)
+
+var (
+	extensionVendor = "Qredo"
+	pluginName      = "qredoplugin"
+)
+
+// Service is the Qredo Plugin service
+type Service struct {
+	defaultservice.Service
+}
+
+// NewService returns a Milagro implementation of Service
+func NewService() *Service {
+	return &Service{}
+}
+
+// Name of the plugin
+func (s *Service) Name() string {
+	return pluginName
+}
+
+// Vendor of the plugin
+func (s *Service) Vendor() string {
+	return extensionVendor
+}
+
+// ValidateOrderRequest checks if the Coin type is valid
+func (s *Service) ValidateOrderRequest(req *api.OrderRequest) error {
+	if _, err := strconv.ParseInt(req.Extension["coin"], 10, 64); err != nil {
+		return errors.Wrap(err, "Failed to Parse Coin Type")
+	}
+
+	return nil
+}
+
+// PrepareOrderPart1 adds the coin type to the order
+func (s *Service) PrepareOrderPart1(order *documents.OrderDoc, reqExtension map[string]string) (fulfillExtension map[string]string, err error) {
+	coin, err := strconv.ParseInt(reqExtension["coin"], 10, 64)
+	if err != nil {
+		return nil, errors.Wrap(err, "Failed to Parse Coin Type")
+	}
+
+	order.Coin = coin
+	return nil, nil
+}
+
+// PrepareOrderResponse gets the updated order and returns the commitment and extension
+func (s *Service) PrepareOrderResponse(orderPart2 *documents.OrderDoc, reqExtension, fulfillExtension map[string]string) (commitment string, extension map[string]string, err error) {
+	pubKeyPart2of2 := orderPart2.OrderPart2.CommitmentPublicKey
+	finalPublicKey, cryptoAddress, err := generateFinalPubKey(s, pubKeyPart2of2, *orderPart2)
+
+	return finalPublicKey, map[string]string{"address": cryptoAddress}, nil
+}
+
+// ProduceBeneficiaryEncryptedData -
+func (s *Service) ProduceBeneficiaryEncryptedData(blsSK []byte, order *documents.OrderDoc, req *api.OrderSecretRequest) (encrypted []byte, extension map[string]string, err error) {
+	//There is no beneficiary supplided in either the Deposit or Redemption
+	if order.BeneficiaryCID == "" && req.BeneficiaryIDDocumentCID == "" {
+		return nil, nil, errors.New("Beneficiary must be supplied")
+	}
+
+	enc, err := adhocEncryptedEnvelopeEncode(s, s.NodeID(), req.BeneficiaryIDDocumentCID, *order, blsSK)
+	return enc, nil, err
+}
+
+// ProduceFinalSecret -
+func (s *Service) ProduceFinalSecret(seed, sikeSK []byte, order, orderPart4 *documents.OrderDoc, req *api.OrderSecretRequest, fulfillSecretRespomse *api.FulfillOrderSecretResponse) (secret, commitment string, extension map[string]string, err error) {
+	//retrieve principal IDDoc
+	principalDocID, err := common.RetrieveIDDocFromIPFS(s.Ipfs, order.PrincipalCID)
+	if err != nil {
+		return "", "", nil, err
+	}
+
+	finalPrivateKey, err := deriveFinalPrivateKey(s, *orderPart4, sikeSK, seed, req.BeneficiaryIDDocumentCID, s.NodeID(), principalDocID.BLSPublicKey)
+	if err != nil {
+		return "", "", nil, err
+	}
+	//Generate Public key & derive crypto address
+	finalPublicKey, finalPublicKeyCompressed, err := cryptowallet.PublicKeyFromPrivate(finalPrivateKey)
+	if err != nil {
+		return "", "", nil, err
+	}
+
+	addressForPublicKey, err := addressForPublicKey(finalPublicKey, int(order.Coin))
+	if err != nil {
+		return "", "", nil, err
+	}
+
+	return finalPrivateKey, finalPublicKeyCompressed, map[string]string{"address": addressForPublicKey}, nil
+}
diff --git a/plugins/qredoplugin.go b/plugins/qredoplugin.go
new file mode 100644
index 0000000..68bf54d
--- /dev/null
+++ b/plugins/qredoplugin.go
@@ -0,0 +1,9 @@
+package plugins
+
+import (
+	"github.com/apache/incubator-milagro-dta/pkg/qredoplugin"
+)
+
+func init() {
+	registerPlugin(qredoplugin.NewService())
+}