Skip to content

Commit 2ff9948

Browse files
committed
# This is a combination of 2 commits.
# This is the 1st commit message: WIP: Add CKD implementation and integrate derivation path in signing sessions # This is the commit message #2: use consulKV for store ckd seed
1 parent 1bad9a7 commit 2ff9948

7 files changed

Lines changed: 249 additions & 9 deletions

File tree

examples/ckd/main.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
9+
"github.com/fystack/mpcium/pkg/client"
10+
"github.com/fystack/mpcium/pkg/config"
11+
"github.com/fystack/mpcium/pkg/event"
12+
"github.com/fystack/mpcium/pkg/logger"
13+
"github.com/fystack/mpcium/pkg/types"
14+
"github.com/google/uuid"
15+
"github.com/nats-io/nats.go"
16+
"github.com/spf13/viper"
17+
)
18+
19+
func main() {
20+
const environment = "dev"
21+
config.InitViperConfig()
22+
logger.Init(environment, true)
23+
24+
natsURL := viper.GetString("nats.url")
25+
natsConn, err := nats.Connect(natsURL)
26+
if err != nil {
27+
logger.Fatal("Failed to connect to NATS", err)
28+
}
29+
defer natsConn.Drain()
30+
defer natsConn.Close()
31+
32+
mpcClient := client.NewMPCClient(client.Options{
33+
NatsConn: natsConn,
34+
KeyPath: "./event_initiator.key",
35+
})
36+
37+
// 2) Once wallet exists, immediately fire a SignTransaction
38+
txID := uuid.New().String()
39+
dummyTx := []byte("deadbeef") // replace with real transaction bytes
40+
41+
txMsg := &types.SignTxMessage{
42+
KeyType: types.KeyTypeEd25519,
43+
WalletID: "739c2f58-8385-4c40-a642-9a8a1e0d336f",
44+
NetworkInternalCode: "solana-devnet",
45+
TxID: txID,
46+
Tx: dummyTx,
47+
DerivationPath: []uint32{1, 2, 3},
48+
}
49+
err = mpcClient.SignTransaction(txMsg)
50+
if err != nil {
51+
logger.Fatal("SignTransaction failed", err)
52+
}
53+
fmt.Printf("SignTransaction(%q) sent, awaiting result...\n", txID)
54+
55+
// 3) Listen for signing results
56+
err = mpcClient.OnSignResult(func(evt event.SigningResultEvent) {
57+
logger.Info("Signing result received",
58+
"txID", evt.TxID,
59+
"signature", fmt.Sprintf("%x", evt.Signature),
60+
)
61+
})
62+
if err != nil {
63+
logger.Fatal("Failed to subscribe to OnSignResult", err)
64+
}
65+
66+
stop := make(chan os.Signal, 1)
67+
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
68+
<-stop
69+
70+
fmt.Println("Shutting down.")
71+
}

pkg/eventconsumer/event_consumer.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error {
335335
msg.TxID,
336336
msg.NetworkInternalCode,
337337
ec.signingResultQueue,
338+
msg.DerivationPath,
338339
)
339340
case types.KeyTypeEd25519:
340341
session, err = ec.node.CreateSigningSession(
@@ -343,6 +344,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error {
343344
msg.TxID,
344345
msg.NetworkInternalCode,
345346
ec.signingResultQueue,
347+
msg.DerivationPath,
346348
)
347349

348350
}

pkg/mpc/ckd.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package mpc
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"fmt"
8+
"math/big"
9+
10+
"github.com/bnb-chain/tss-lib/v2/common"
11+
"github.com/bnb-chain/tss-lib/v2/crypto"
12+
"github.com/bnb-chain/tss-lib/v2/crypto/ckd"
13+
"github.com/bnb-chain/tss-lib/v2/ecdsa/keygen"
14+
"github.com/fystack/mpcium/pkg/infra"
15+
"github.com/fystack/mpcium/pkg/logger"
16+
"github.com/hashicorp/consul/api"
17+
18+
"github.com/btcsuite/btcd/chaincfg"
19+
)
20+
21+
// Child Key Derivation
22+
type CKD struct {
23+
Store infra.ConsulKV
24+
ChainCode []byte
25+
Path []uint32
26+
}
27+
28+
func NewCKD() *CKD {
29+
ckd := &CKD{
30+
Store: infra.GetConsulClient("development").KV(),
31+
}
32+
ckd.initializeChainCode()
33+
return ckd
34+
}
35+
36+
func (c *CKD) UpdateSinglePublicKeyAndAdjustBigXj(
37+
keyDerivationDelta *big.Int,
38+
key *keygen.LocalPartySaveData,
39+
extendedChildPk *ecdsa.PublicKey,
40+
ec elliptic.Curve,
41+
) error {
42+
var err error
43+
44+
// Compute g^delta
45+
gDelta := crypto.ScalarBaseMult(ec, keyDerivationDelta)
46+
47+
// Update the public key
48+
key.ECDSAPub, err = crypto.NewECPoint(ec, extendedChildPk.X, extendedChildPk.Y)
49+
if err != nil {
50+
common.Logger.Errorf("error creating new extended child public key")
51+
return err
52+
}
53+
54+
// Update each BigXj[i] := BigXj[i] + g^delta
55+
for j := range key.BigXj {
56+
key.BigXj[j], err = key.BigXj[j].Add(gDelta)
57+
if err != nil {
58+
common.Logger.Errorf("error in delta operation")
59+
return err
60+
}
61+
}
62+
63+
return nil
64+
}
65+
66+
func (c *CKD) Derive(masterPub *crypto.ECPoint, path []uint32, curve elliptic.Curve) (*big.Int, *ckd.ExtendedKey, error) {
67+
return c.derivingPubkeyFromPath(masterPub, c.ChainCode, path, curve)
68+
}
69+
70+
func (c *CKD) derivingPubkeyFromPath(masterPub *crypto.ECPoint, chainCode []byte, path []uint32, ec elliptic.Curve) (*big.Int, *ckd.ExtendedKey, error) {
71+
// build ecdsa key pair
72+
pk := ecdsa.PublicKey{
73+
Curve: ec,
74+
X: masterPub.X(),
75+
Y: masterPub.Y(),
76+
}
77+
78+
net := &chaincfg.MainNetParams
79+
extendedParentPk := &ckd.ExtendedKey{
80+
PublicKey: pk,
81+
Depth: 0,
82+
ChildIndex: 0,
83+
ChainCode: chainCode[:],
84+
ParentFP: []byte{0x00, 0x00, 0x00, 0x00},
85+
Version: net.HDPrivateKeyID[:],
86+
}
87+
88+
return ckd.DeriveChildKeyFromHierarchy(path, extendedParentPk, ec.Params().N, ec)
89+
}
90+
91+
func (c *CKD) initializeChainCode() error {
92+
logger.Info("Initializing chain code")
93+
94+
// Try to get chain code from store
95+
val, _, err := c.Store.Get("chain_code", nil)
96+
if err == nil && val != nil && len(val.Value) == 32 {
97+
// Found existing chain code
98+
c.ChainCode = make([]byte, 32)
99+
copy(c.ChainCode, val.Value)
100+
logger.Info("Loaded existing chain code", "chainCode", c.ChainCode)
101+
return nil
102+
}
103+
104+
// Not found or invalid: generate new chain code
105+
chainCode := make([]byte, 32)
106+
max := new(big.Int).Lsh(big.NewInt(1), 256)
107+
max.Sub(max, big.NewInt(1))
108+
fillBytes(common.GetRandomPositiveInt(rand.Reader, max), chainCode)
109+
110+
// Save to store
111+
_, err = c.Store.Put(&api.KVPair{Key: "chain_code", Value: chainCode}, nil)
112+
if err != nil {
113+
return fmt.Errorf("failed to store chain code: %w", err)
114+
}
115+
116+
// Assign to CKD struct
117+
c.ChainCode = make([]byte, 32)
118+
copy(c.ChainCode, chainCode)
119+
logger.Info("Generated new chain code", "chainCode", c.ChainCode)
120+
return nil
121+
}
122+
123+
func fillBytes(x *big.Int, buf []byte) []byte {
124+
b := x.Bytes()
125+
if len(b) > len(buf) {
126+
panic("buffer too small")
127+
}
128+
offset := len(buf) - len(b)
129+
for i := range buf {
130+
if i < offset {
131+
buf[i] = 0
132+
} else {
133+
buf[i] = b[i-offset]
134+
}
135+
}
136+
return buf
137+
}

pkg/mpc/ecdsa_signing_session.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type ecdsaSigningSession struct {
3535
tx *big.Int
3636
txID string
3737
networkInternalCode string
38+
derivationPath []uint32
39+
ckd *CKD
3840
}
3941

4042
func newECDSASigningSession(
@@ -52,7 +54,9 @@ func newECDSASigningSession(
5254
keyinfoStore keyinfo.Store,
5355
resultQueue messaging.MessageQueue,
5456
identityStore identity.Store,
57+
derivationPath []uint32,
5558
) *ecdsaSigningSession {
59+
5660
return &ecdsaSigningSession{
5761
session: session{
5862
walletID: walletID,
@@ -85,7 +89,10 @@ func newECDSASigningSession(
8589
endCh: make(chan *common.SignatureData),
8690
txID: txID,
8791
networkInternalCode: networkInternalCode,
92+
derivationPath: derivationPath,
93+
ckd: NewCKD(),
8894
}
95+
8996
}
9097

9198
func (s *ecdsaSigningSession) Init(tx *big.Int) error {
@@ -126,7 +133,23 @@ func (s *ecdsaSigningSession) Init(tx *big.Int) error {
126133
return errors.Wrap(err, "Failed to unmarshal wallet data")
127134
}
128135

129-
s.party = signing.NewLocalParty(tx, params, data, s.outCh, s.endCh)
136+
if len(s.derivationPath) > 0 {
137+
logger.Info("Deriving key from derivation path", "derivationPath", s.derivationPath)
138+
il, extendedChildPk, errorDerivation := s.ckd.Derive(data.ECDSAPub, s.derivationPath, tss.S256())
139+
if errorDerivation != nil {
140+
return errors.Wrap(errorDerivation, "Failed to derive key")
141+
}
142+
keyDerivationDelta := il
143+
err = s.ckd.UpdateSinglePublicKeyAndAdjustBigXj(keyDerivationDelta, &data, &extendedChildPk.PublicKey, tss.S256())
144+
if err != nil {
145+
return errors.Wrap(err, "Failed to update public key")
146+
}
147+
148+
s.party = signing.NewLocalPartyWithKDD(tx, params, data, keyDerivationDelta, s.outCh, s.endCh, 0)
149+
150+
} else {
151+
s.party = signing.NewLocalParty(tx, params, data, s.outCh, s.endCh)
152+
}
130153
s.data = &data
131154
s.version = keyInfo.Version
132155
s.tx = tx

pkg/mpc/eddsa_signing_session.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type eddsaSigningSession struct {
2727
tx *big.Int
2828
txID string
2929
networkInternalCode string
30+
derivationPath []uint32
3031
}
3132

3233
func newEDDSASigningSession(
@@ -43,6 +44,7 @@ func newEDDSASigningSession(
4344
keyinfoStore keyinfo.Store,
4445
resultQueue messaging.MessageQueue,
4546
identityStore identity.Store,
47+
derivationPath []uint32,
4648
) *eddsaSigningSession {
4749
return &eddsaSigningSession{
4850
session: session{
@@ -76,6 +78,7 @@ func newEDDSASigningSession(
7678
endCh: make(chan *common.SignatureData),
7779
txID: txID,
7880
networkInternalCode: networkInternalCode,
81+
derivationPath: derivationPath,
7982
}
8083
}
8184

pkg/mpc/node.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ type Node struct {
4141
keyinfoStore keyinfo.Store
4242
ecdsaPreParams []*keygen.LocalPreParams
4343
identityStore identity.Store
44-
45-
peerRegistry PeerRegistry
44+
chainCode []byte
45+
peerRegistry PeerRegistry
4646
}
4747

4848
func PartyIDToRoutingDest(partyID *tss.PartyID) string {
@@ -165,6 +165,7 @@ func (p *Node) CreateSigningSession(
165165
txID string,
166166
networkInternalCode string,
167167
resultQueue messaging.MessageQueue,
168+
derivationPath []uint32,
168169
) (SigningSession, error) {
169170
version := p.getVersion(sessionType, walletID)
170171
keyInfo, err := p.getKeyInfo(sessionType, walletID)
@@ -211,6 +212,7 @@ func (p *Node) CreateSigningSession(
211212
p.keyinfoStore,
212213
resultQueue,
213214
p.identityStore,
215+
derivationPath,
214216
), nil
215217

216218
case SessionTypeEDDSA:
@@ -228,6 +230,7 @@ func (p *Node) CreateSigningSession(
228230
p.keyinfoStore,
229231
resultQueue,
230232
p.identityStore,
233+
derivationPath,
231234
), nil
232235
}
233236

pkg/types/initiator_msg.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ type GenerateKeyMessage struct {
2525
}
2626

2727
type SignTxMessage struct {
28-
KeyType KeyType `json:"key_type"`
29-
WalletID string `json:"wallet_id"`
30-
NetworkInternalCode string `json:"network_internal_code"`
31-
TxID string `json:"tx_id"`
32-
Tx []byte `json:"tx"`
33-
Signature []byte `json:"signature"`
28+
KeyType KeyType `json:"key_type"`
29+
WalletID string `json:"wallet_id"`
30+
NetworkInternalCode string `json:"network_internal_code"`
31+
TxID string `json:"tx_id"`
32+
Tx []byte `json:"tx"`
33+
Signature []byte `json:"signature"`
34+
DerivationPath []uint32 `json:"derivation_path"`
3435
}
3536

3637
type ResharingMessage struct {

0 commit comments

Comments
 (0)