From 330248e3f953a0dea4c37410bbef62cd9ef235f9 Mon Sep 17 00:00:00 2001 From: Cedric Fung Date: Mon, 9 Oct 2023 07:44:29 +0000 Subject: [PATCH 1/2] keeper adds mixin kernel as an app --- apps/bitcoin/common.go | 4 +- apps/bitcoin/transaction.go | 3 +- apps/mixin/account.go | 83 ++++ apps/mixin/common.go | 22 ++ apps/mixin/transaction.go | 99 +++++ common/mixin.go | 22 ++ common/request.go | 15 +- common/util.go | 6 + keeper/bitcoin.go | 34 +- keeper/common.go | 26 +- keeper/deposit.go | 18 +- keeper/group.go | 39 +- keeper/keeper_test.go | 108 +++--- keeper/mixin.go | 730 +++++++++++++++++++++++++++++++++++- keeper/mixin_test.go | 517 +++++++++++++++++++++++++ keeper/network.go | 30 +- keeper/node.go | 3 +- keeper/signer.go | 7 +- keeper/store/mixin.go | 139 +++++++ keeper/store/schema.sql | 21 ++ keeper/store/signature.go | 7 +- keeper/store/transaction.go | 99 +++-- observer/bitcoin.go | 8 +- observer/holder.go | 18 +- observer/keeper.go | 8 +- observer/key.go | 4 +- observer/node.go | 4 +- signer/frost_test.go | 84 ++--- signer/group.go | 29 +- signer/mixin_test.go | 5 +- signer/test.go | 49 ++- 31 files changed, 2016 insertions(+), 225 deletions(-) create mode 100644 apps/mixin/account.go create mode 100644 apps/mixin/common.go create mode 100644 apps/mixin/transaction.go create mode 100644 keeper/mixin_test.go create mode 100644 keeper/store/mixin.go diff --git a/apps/bitcoin/common.go b/apps/bitcoin/common.go index 65b1ee31..31fef600 100644 --- a/apps/bitcoin/common.go +++ b/apps/bitcoin/common.go @@ -164,8 +164,8 @@ func IsInsufficientInputError(err error) bool { return err != nil && strings.HasPrefix(err.Error(), "insufficient ") } -func buildInsufficientInputError(cat string, inSatoshi, outSatoshi int64) error { - return fmt.Errorf("insufficient %s %d %d", cat, inSatoshi, outSatoshi) +func BuildInsufficientInputError(cat, inSatoshi, outSatoshi string) error { + return fmt.Errorf("insufficient %s %s %s", cat, inSatoshi, outSatoshi) } func writeBytes(enc *common.Encoder, b []byte) { diff --git a/apps/bitcoin/transaction.go b/apps/bitcoin/transaction.go index ad72b6f4..cfdca3ac 100644 --- a/apps/bitcoin/transaction.go +++ b/apps/bitcoin/transaction.go @@ -242,7 +242,8 @@ func BuildPartiallySignedTransaction(mainInputs []*Input, outputs []*Output, rid outputSatoshi = outputSatoshi + out.Satoshi } if outputSatoshi > mainSatoshi { - return nil, buildInsufficientInputError("main", mainSatoshi, outputSatoshi) + err := BuildInsufficientInputError("main", fmt.Sprint(mainSatoshi), fmt.Sprint(outputSatoshi)) + return nil, err } mainChange := mainSatoshi - outputSatoshi if mainChange > ValueDust(chain) { diff --git a/apps/mixin/account.go b/apps/mixin/account.go new file mode 100644 index 00000000..3ab4e215 --- /dev/null +++ b/apps/mixin/account.go @@ -0,0 +1,83 @@ +package mixin + +import ( + "encoding/hex" + "fmt" + + "github.com/MixinNetwork/mixin/common" + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/multi-party-sig/pkg/math/curve" +) + +func VerifyPublicKey(pub string) error { + key, err := crypto.KeyFromString(pub) + if err != nil { + return err + } + if !key.CheckKey() { + return fmt.Errorf("invalid mixin public key %s", pub) + } + return nil +} + +func VerifySignature(public string, msg, sig []byte) error { + var msig crypto.Signature + if len(sig) != len(msig) { + return fmt.Errorf("invalid mixin signature %x", sig) + } + copy(msig[:], sig) + key, err := crypto.KeyFromString(public) + if err != nil { + return err + } + if key.Verify(msg, msig) { + return nil + } + return fmt.Errorf("mixin.VerifySignature(%s, %x, %x)", public, msg, sig) +} + +func DeriveKey(signer string, mask []byte) string { + group := curve.Edwards25519{} + r := group.NewScalar() + err := r.UnmarshalBinary(mask) + if err != nil { + panic(err) + } + key, err := crypto.KeyFromString(signer) + if err != nil || !key.CheckKey() { + panic(signer) + } + P := group.NewPoint() + err = P.UnmarshalBinary(key[:]) + if err != nil { + panic(err) + } + P = r.ActOnBase().Add(P) + b, err := P.MarshalBinary() + if err != nil { + panic(err) + } + return hex.EncodeToString(b) +} + +func ParseAddress(s string) (*common.Address, error) { + addr, err := common.NewAddressFromString(s) + return &addr, err +} + +func BuildAddress(holder, signer, observer string) *common.Address { + for _, k := range []string{holder, signer, observer} { + err := VerifyPublicKey(k) + if err != nil { + panic(k) + } + } + seed := crypto.NewHash([]byte(holder + signer + observer)) + view := crypto.NewKeyFromSeed(append(seed[:], seed[:]...)) + publicSpend, _ := crypto.KeyFromString(signer) + return &common.Address{ + PublicSpendKey: publicSpend, + PublicViewKey: view.Public(), + PrivateViewKey: view, + } +} diff --git a/apps/mixin/common.go b/apps/mixin/common.go new file mode 100644 index 00000000..05653087 --- /dev/null +++ b/apps/mixin/common.go @@ -0,0 +1,22 @@ +package mixin + +import ( + "bytes" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +const ( + ChainMixinKernel = 3 + ValuePrecision = 8 + ValueDust = 10000 +) + +func HashMessageForSignature(msg string) []byte { + var buf bytes.Buffer + prefix := "Mixin Signed Message:\n" + _ = wire.WriteVarString(&buf, 0, prefix) + _ = wire.WriteVarString(&buf, 0, msg) + return chainhash.DoubleHashB(buf.Bytes()) +} diff --git a/apps/mixin/transaction.go b/apps/mixin/transaction.go new file mode 100644 index 00000000..cb257116 --- /dev/null +++ b/apps/mixin/transaction.go @@ -0,0 +1,99 @@ +package mixin + +import ( + "fmt" + + "github.com/MixinNetwork/mixin/common" + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/safe/apps/bitcoin" + "github.com/gofrs/uuid/v5" + "github.com/shopspring/decimal" +) + +type Input struct { + TransactionHash string + Index uint32 + Amount decimal.Decimal + Asset crypto.Hash + Mask crypto.Key +} + +type Output struct { + Address *common.Address + Amount decimal.Decimal +} + +func ParseTransactionDepositOutput(holder, signer, observer string, mtx *common.VersionedTransaction, index int) (*Input, string) { + if len(mtx.Outputs) < index+1 { + return nil, "" + } + out := mtx.Outputs[index] + if out.Type != common.OutputTypeScript { + return nil, "" + } + if out.Script.String() != "fffe01" { + return nil, "" + } + if len(out.Keys) != 1 { + return nil, "" + } + addr := BuildAddress(holder, signer, observer) + pub := crypto.ViewGhostOutputKey(out.Keys[0], &addr.PrivateViewKey, &out.Mask, uint64(index)) + if pub.String() != addr.PublicSpendKey.String() { + return nil, "" + } + input := &Input{ + TransactionHash: mtx.PayloadHash().String(), + Index: uint32(index), + Amount: decimal.RequireFromString(out.Amount.String()), + Asset: mtx.Asset, + Mask: out.Mask, + } + return input, addr.String() +} + +func BuildPartiallySignedTransaction(mainInputs []*Input, outputs []*Output, rid string, holder, signer, observer string) (*common.VersionedTransaction, error) { + var input, output decimal.Decimal + tx := common.NewTransactionV4(mainInputs[0].Asset) + for _, in := range mainInputs { + if in.Asset != tx.Asset { + panic(in.Asset.String()) + } + input = input.Add(in.Amount) + hash, err := crypto.HashFromString(in.TransactionHash) + if err != nil { + panic(in.TransactionHash) + } + tx.AddInput(hash, int(in.Index)) + } + + si := crypto.NewHash([]byte("SEED:" + holder + signer + observer + rid)) + si = crypto.NewHash(append(tx.AsVersioned().PayloadMarshal(), si[:]...)) + for i, out := range outputs { + output = output.Add(out.Amount) + script := common.NewThresholdScript(1) + amount := common.NewIntegerFromString(out.Amount.String()) + os := fmt.Sprintf("%x:%s:%s:%d", si[:], out.Address.String(), out.Amount.String(), i) + seed := crypto.NewHash([]byte(os)) + tx.AddScriptOutput([]*common.Address{out.Address}, script, amount, append(seed[:], seed[:]...)) + } + + if input.Cmp(output) < 0 { + return nil, bitcoin.BuildInsufficientInputError("main", input.String(), output.String()) + } + change := input.Sub(output) + addr := BuildAddress(holder, signer, observer) + if change.IsPositive() { + script := common.NewThresholdScript(1) + amount := common.NewIntegerFromString(change.String()) + seed := crypto.NewHash([]byte(fmt.Sprintf("%x:%d", si[:], len(outputs)))) + tx.AddScriptOutput([]*common.Address{addr}, script, amount, append(seed[:], seed[:]...)) + } + + tx.Extra = uuid.Must(uuid.FromString(rid)).Bytes() + return tx.AsVersioned(), nil +} + +func ParsePartiallySignedTransaction(b []byte) (*common.VersionedTransaction, error) { + return common.UnmarshalVersionedTransaction(b) +} diff --git a/common/mixin.go b/common/mixin.go index e768521f..fde209dc 100644 --- a/common/mixin.go +++ b/common/mixin.go @@ -21,6 +21,8 @@ import ( "github.com/shopspring/decimal" ) +type VersionedTransaction = common.VersionedTransaction + // TODO the output should include the snapshot signature, then it can just be // verified against the active kernel nodes public key func VerifyKernelTransaction(rpc string, out *mtg.Output, timeout time.Duration) error { @@ -144,6 +146,26 @@ func ReadKernelTransaction(rpc string, tx crypto.Hash) (*common.VersionedTransac return common.UnmarshalVersionedTransaction(hex) } +func ReadKernelSnapshot(rpc string, id crypto.Hash) (*common.SnapshotWithTopologicalOrder, error) { + raw, err := callMixinRPC(rpc, "getsnapshot", []any{id.String()}) + if err != nil { + return nil, err + } + var snap map[string]any + err = json.Unmarshal(raw, &snap) + if err != nil { + return nil, err + } + if snap["hex"] == nil { + return nil, fmt.Errorf("snap %s not found in kernel", id) + } + hex, err := hex.DecodeString(snap["hex"].(string)) + if err != nil { + return nil, err + } + return common.UnmarshalVersionedSnapshot(hex) +} + func callMixinRPC(node, method string, params []any) ([]byte, error) { client := &http.Client{Timeout: 20 * time.Second} diff --git a/common/request.go b/common/request.go index fb6ac303..2a22a756 100644 --- a/common/request.go +++ b/common/request.go @@ -8,6 +8,7 @@ import ( "github.com/MixinNetwork/mixin/common" "github.com/MixinNetwork/mixin/crypto" "github.com/MixinNetwork/safe/apps/bitcoin" + "github.com/MixinNetwork/safe/apps/mixin" "github.com/MixinNetwork/trusted-group/mtg" "github.com/gofrs/uuid/v5" "github.com/shopspring/decimal" @@ -44,11 +45,12 @@ const ( ActionBitcoinSafeCloseAccount = 115 // For Mixin Kernel mainnet - ActionMixinSafeProposeAccount = 120 - ActionMixinSafeApproveAccount = 121 - ActionMixinSafeProposeTransaction = 122 - ActionMixinSafeApproveTransaction = 123 - ActionMixinSafeRevokeTransaction = 124 + ActionMixinKernelSafeProposeAccount = 120 + ActionMixinKernelSafeApproveAccount = 121 + ActionMixinKernelSafeProposeTransaction = 122 + ActionMixinKernelSafeApproveTransaction = 123 + ActionMixinKernelSafeRevokeTransaction = 124 + ActionMixinKernelSafeCloseAccount = 125 // For all Ethereum like chains ActionEthereumSafeProposeAccount = 130 @@ -116,6 +118,7 @@ func DecodeRequest(out *mtg.Output, b []byte, role uint8) (*Request, error) { func (req *Request) ParseMixinRecipient(extra []byte) (*AccountProposal, error) { switch req.Action { case ActionBitcoinSafeProposeAccount: + case ActionMixinKernelSafeProposeAccount: case ActionEthereumSafeProposeAccount: default: panic(req.Action) @@ -191,6 +194,8 @@ func (r *Request) VerifyFormat() error { switch r.Curve { case CurveSecp256k1ECDSABitcoin, CurveSecp256k1ECDSALitecoin: return bitcoin.VerifyHolderKey(r.Holder) + case CurveEdwards25519Mixin: + return mixin.VerifyPublicKey(r.Holder) default: return fmt.Errorf("invalid request curve %v", r) } diff --git a/common/util.go b/common/util.go index bf0284e1..ae4a4d1b 100644 --- a/common/util.go +++ b/common/util.go @@ -8,6 +8,8 @@ import ( "encoding/json" "os" "strings" + + "github.com/fox-one/mixin-sdk-go" ) const ( @@ -43,6 +45,10 @@ func Fingerprint(public string) []byte { return sum[:8] } +func UniqueId(a, b string) string { + return mixin.UniqueConversationID(a, b) +} + func EnableTestEnvironment(ctx context.Context) context.Context { return context.WithValue(ctx, contextKeyEnvironment, "test") } diff --git a/keeper/bitcoin.go b/keeper/bitcoin.go index c827ec2a..699e5f93 100644 --- a/keeper/bitcoin.go +++ b/keeper/bitcoin.go @@ -17,7 +17,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/wire" - "github.com/fox-one/mixin-sdk-go" "github.com/gofrs/uuid/v5" "github.com/shopspring/decimal" ) @@ -154,8 +153,8 @@ func (node *Node) processBitcoinSafeCloseAccount(ctx context.Context, req *commo } total = total + bo.Satoshi - pending, err := node.checkBitcoinUTXOSignaturePending(ctx, txHash, idx, req) - logger.Printf("node.checkBitcoinUTXOSignaturePending(%s, %d) => %t %v", txHash, idx, pending, err) + pending, err := node.checkTransactionIndexSignaturePending(ctx, txHash, idx, req) + logger.Printf("node.checkTransactionIndexSignaturePending(%s, %d) => %t %v", txHash, idx, pending, err) if err != nil { return err } else if pending { @@ -172,7 +171,7 @@ func (node *Node) processBitcoinSafeCloseAccount(ctx context.Context, req *commo CreatedAt: req.CreatedAt, UpdatedAt: req.CreatedAt, } - sr.RequestId = mixin.UniqueConversationID(req.Id, sr.Message) + sr.RequestId = common.UniqueId(req.Id, sr.Message) requests = append(requests, sr) } if total != msgTx.TxOut[0].Value { @@ -266,7 +265,8 @@ func (node *Node) tryToCloseBitcoinAccountsFromUnannouncedRecovery(ctx context.C CreatedAt: req.CreatedAt, UpdatedAt: req.CreatedAt, } - err = node.store.CloseAccountByTransactionWithRequest(ctx, tx, inputs, common.RequestStatePending) + transacionInputs := store.TransactionInputsFromBitcoin(inputs) + err = node.store.CloseAccountByTransactionWithRequest(ctx, tx, transacionInputs, common.RequestStatePending) return []string{safe.Holder}, err } @@ -296,7 +296,8 @@ func (node *Node) closeBitcoinAccountWithHolder(ctx context.Context, req *common CreatedAt: req.CreatedAt, UpdatedAt: req.CreatedAt, } - return node.store.CloseAccountByTransactionWithRequest(ctx, tx, mainInputs, common.RequestStateDone) + transacionInputs := store.TransactionInputsFromBitcoin(mainInputs) + return node.store.CloseAccountByTransactionWithRequest(ctx, tx, transacionInputs, common.RequestStateDone) } func (node *Node) processBitcoinSafeProposeAccount(ctx context.Context, req *common.Request) error { @@ -579,6 +580,7 @@ func (node *Node) processBitcoinSafeProposeTransaction(ctx context.Context, req if err != nil { return node.store.FailRequest(ctx, req.Id) } + var total decimal.Decimal for _, rp := range recipients { script, err := bitcoin.ParseAddress(rp[0], safe.Chain) logger.Printf("bitcoin.ParseAddress(%s, %d) => %x %v", string(extra), safe.Chain, script, err) @@ -592,11 +594,15 @@ func (node *Node) processBitcoinSafeProposeTransaction(ctx context.Context, req if amt.Cmp(plan.TransactionMinimum) < 0 { return node.store.FailRequest(ctx, req.Id) } + total = total.Add(amt) outputs = append(outputs, &bitcoin.Output{ Address: rp[0], Satoshi: bitcoin.ParseSatoshi(amt.String()), }) } + if !total.Equal(req.Amount) { + return node.store.FailRequest(ctx, req.Id) + } } else { script, err := bitcoin.ParseAddress(string(extra[16:]), safe.Chain) logger.Printf("bitcoin.ParseAddress(%s, %d) => %x %v", string(extra), safe.Chain, script, err) @@ -645,13 +651,15 @@ func (node *Node) processBitcoinSafeProposeTransaction(ctx context.Context, req RawTransaction: hex.EncodeToString(extra), Holder: req.Holder, Chain: safe.Chain, + AssetId: assetId, State: common.RequestStateInitial, Data: string(data), RequestId: req.Id, CreatedAt: req.CreatedAt, UpdatedAt: req.CreatedAt, } - return node.store.WriteTransactionWithRequest(ctx, tx, mainInputs) + transacionInputs := store.TransactionInputsFromBitcoin(mainInputs) + return node.store.WriteTransactionWithRequest(ctx, tx, transacionInputs) } func (node *Node) processBitcoinSafeRevokeTransaction(ctx context.Context, req *common.Request) error { @@ -806,8 +814,8 @@ func (node *Node) processBitcoinSafeApproveTransaction(ctx context.Context, req continue } - pending, err := node.checkBitcoinUTXOSignaturePending(ctx, tx.TransactionHash, idx, req) - logger.Printf("node.checkBitcoinUTXOSignaturePending(%s, %d) => %t %v", tx.TransactionHash, idx, pending, err) + pending, err := node.checkTransactionIndexSignaturePending(ctx, tx.TransactionHash, idx, req) + logger.Printf("node.checkTransactionIndexSignaturePending(%s, %d) => %t %v", tx.TransactionHash, idx, pending, err) if err != nil { return err } else if pending { @@ -824,7 +832,7 @@ func (node *Node) processBitcoinSafeApproveTransaction(ctx context.Context, req CreatedAt: req.CreatedAt, UpdatedAt: req.CreatedAt, } - sr.RequestId = mixin.UniqueConversationID(req.Id, sr.Message) + sr.RequestId = common.UniqueId(req.Id, sr.Message) requests = append(requests, sr) } err = node.store.WriteSignatureRequestsWithRequest(ctx, requests, tx.TransactionHash, req) @@ -922,7 +930,7 @@ func (node *Node) processBitcoinSafeSignatureResponse(ctx context.Context, req * } exk := node.writeStorageOrPanic(ctx, []byte(common.Base91Encode(spsbt.Marshal()))) - id := mixin.UniqueConversationID(old.TransactionHash, hex.EncodeToString(exk[:])) + id := common.UniqueId(old.TransactionHash, hex.EncodeToString(exk[:])) typ := byte(common.ActionBitcoinSafeApproveTransaction) crv := BitcoinChainCurve(safe.Chain) err = node.sendObserverResponseWithReferences(ctx, id, typ, crv, exk) @@ -930,7 +938,7 @@ func (node *Node) processBitcoinSafeSignatureResponse(ctx context.Context, req * return fmt.Errorf("node.sendObserverResponse(%s, %x) => %v", id, exk, err) } raw := hex.EncodeToString(spsbt.Marshal()) - err = node.store.FinishTransactionSignaturesWithRequest(ctx, old.TransactionHash, raw, req, int64(len(msgTx.TxIn))) + err = node.store.FinishTransactionSignaturesWithRequest(ctx, old.TransactionHash, raw, req, int64(len(msgTx.TxIn)), tx.Chain) logger.Printf("store.FinishTransactionSignaturesWithRequest(%s, %s, %v) => %v", old.TransactionHash, raw, req, err) return err } @@ -973,7 +981,7 @@ func (node *Node) deriveBIP32WithPath(ctx context.Context, public string, path8 return sdk, err } -func (node *Node) checkBitcoinUTXOSignaturePending(ctx context.Context, hash string, index int, req *common.Request) (bool, error) { +func (node *Node) checkTransactionIndexSignaturePending(ctx context.Context, hash string, index int, req *common.Request) (bool, error) { old, err := node.store.ReadSignatureRequestByTransactionIndex(ctx, hash, index) if err != nil { return false, err diff --git a/keeper/common.go b/keeper/common.go index d377cabc..3c7eb8a3 100644 --- a/keeper/common.go +++ b/keeper/common.go @@ -9,22 +9,24 @@ import ( "github.com/MixinNetwork/mixin/domains/mvm" "github.com/MixinNetwork/mixin/logger" "github.com/MixinNetwork/safe/apps/bitcoin" + "github.com/MixinNetwork/safe/apps/mixin" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/common/abi" "github.com/shopspring/decimal" ) const ( - SafeChainBitcoin = bitcoin.ChainBitcoin - SafeChainEthereum = 2 - SafeChainMixin = 3 - SafeChainMVM = 4 - SafeChainLitecoin = bitcoin.ChainLitecoin + SafeChainBitcoin = bitcoin.ChainBitcoin + SafeChainEthereum = 2 + SafeChainMixinKernel = mixin.ChainMixinKernel + SafeChainMVM = 4 + SafeChainLitecoin = bitcoin.ChainLitecoin - SafeBitcoinChainId = "c6d0c728-2624-429b-8e0d-d9d19b6592fa" - SafeEthereumChainId = "43d61dcd-e413-450d-80b8-101d5e903357" - SafeMVMChainId = "a0ffd769-5850-4b48-9651-d2ae44a3e64d" - SafeLitecoinChainId = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8" + SafeBitcoinChainId = "c6d0c728-2624-429b-8e0d-d9d19b6592fa" + SafeEthereumChainId = "43d61dcd-e413-450d-80b8-101d5e903357" + SafeMVMChainId = "a0ffd769-5850-4b48-9651-d2ae44a3e64d" + SafeLitecoinChainId = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8" + SafeMixinKernelAssetId = "c94ac88f-4671-3976-b60a-09064f1811e8" SafeNetworkInfoTimeout = 3 * time.Minute SafeSignatureTimeout = 10 * time.Minute @@ -34,6 +36,10 @@ const ( SafeStateClosed = common.RequestStateFailed ) +func mixinDefaultDerivationPath() []byte { + return []byte{0, 0, 0, 0} +} + func bitcoinDefaultDerivationPath() []byte { return []byte{2, 0, 0, 0} } @@ -71,7 +77,7 @@ func (node *Node) refundAndFailRequest(ctx context.Context, req *common.Request, func (node *Node) bondMaxSupply(ctx context.Context, chain byte, assetId string) decimal.Decimal { switch assetId { - case SafeBitcoinChainId, SafeLitecoinChainId: + case SafeBitcoinChainId, SafeLitecoinChainId, SafeMixinKernelAssetId: return decimal.RequireFromString("115792089237316195423570985008687907853269984665640564039457.58400791") default: return decimal.RequireFromString("115792089237316195423570985008687907853269984665640564039457.58400791") diff --git a/keeper/deposit.go b/keeper/deposit.go index a8a037d3..ef730727 100644 --- a/keeper/deposit.go +++ b/keeper/deposit.go @@ -33,18 +33,25 @@ func parseDepositExtra(req *common.Request) (*Deposit, error) { Chain: extra[0], Asset: uuid.Must(uuid.FromBytes(extra[1:17])).String(), } - if deposit.Chain != BitcoinCurveChain(req.Curve) { - panic(req.Id) - } extra = extra[17:] switch deposit.Chain { case SafeChainBitcoin, SafeChainLitecoin: + if deposit.Chain != BitcoinCurveChain(req.Curve) { + panic(req.Id) + } deposit.Hash = hex.EncodeToString(extra[0:32]) deposit.Index = binary.BigEndian.Uint64(extra[32:40]) deposit.Amount = new(big.Int).SetBytes(extra[40:]) if !deposit.Amount.IsInt64() { return nil, fmt.Errorf("invalid deposit amount %s", deposit.Amount.String()) } + case SafeChainMixinKernel: + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Id) + } + deposit.Hash = hex.EncodeToString(extra[0:32]) + deposit.Index = binary.BigEndian.Uint64(extra[32:40]) + deposit.Amount = new(big.Int).SetBytes(extra[40:]) case SafeChainEthereum: deposit.Hash = "0x" + hex.EncodeToString(extra[0:32]) deposit.Index = binary.BigEndian.Uint64(extra[32:40]) @@ -96,7 +103,7 @@ func (node *Node) CreateHolderDeposit(ctx context.Context, req *common.Request) if err != nil { return fmt.Errorf("node.fetchAssetMeta(%s) => %v", deposit.Asset, err) } - if asset.Chain != safe.Chain { + if asset.Chain != safe.Chain && safe.Chain != SafeChainMixinKernel { panic(asset.AssetId) } @@ -111,6 +118,8 @@ func (node *Node) CreateHolderDeposit(ctx context.Context, req *common.Request) switch deposit.Chain { case SafeChainBitcoin, SafeChainLitecoin: return node.doBitcoinHolderDeposit(ctx, req, deposit, safe, bond.AssetId, asset, plan.TransactionMinimum) + case SafeChainMixinKernel: + return node.doMixinKernelHolderDeposit(ctx, req, deposit, safe, bond.AssetId, plan.TransactionMinimum) case SafeChainEthereum: panic(0) default: @@ -119,6 +128,7 @@ func (node *Node) CreateHolderDeposit(ctx context.Context, req *common.Request) } // FIXME Keeper should deny new deposits when too many unspent outputs +// for mixin kernel this value is 256 func (node *Node) doBitcoinHolderDeposit(ctx context.Context, req *common.Request, deposit *Deposit, safe *store.Safe, bondId string, asset *store.Asset, minimum decimal.Decimal) error { if asset.Decimals != bitcoin.ValuePrecision { panic(asset.Decimals) diff --git a/keeper/group.go b/keeper/group.go index 497043ca..8e7697d7 100644 --- a/keeper/group.go +++ b/keeper/group.go @@ -8,6 +8,7 @@ import ( "github.com/MixinNetwork/mixin/logger" "github.com/MixinNetwork/safe/apps/bitcoin" + "github.com/MixinNetwork/safe/apps/mixin" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/common/abi" "github.com/MixinNetwork/trusted-group/mtg" @@ -41,6 +42,12 @@ func (node *Node) ProcessOutput(ctx context.Context, out *mtg.Output) bool { case common.ActionBitcoinSafeApproveTransaction: case common.ActionBitcoinSafeRevokeTransaction: case common.ActionBitcoinSafeCloseAccount: + case common.ActionMixinKernelSafeProposeAccount: + case common.ActionMixinKernelSafeApproveAccount: + case common.ActionMixinKernelSafeProposeTransaction: + case common.ActionMixinKernelSafeApproveTransaction: + case common.ActionMixinKernelSafeRevokeTransaction: + case common.ActionMixinKernelSafeCloseAccount: default: return false } @@ -79,17 +86,17 @@ func (node *Node) getActionRole(act byte) byte { return common.RequestRoleObserver case common.ActionObserverSetOperationParams: return common.RequestRoleObserver - case common.ActionBitcoinSafeProposeAccount: + case common.ActionBitcoinSafeProposeAccount, common.ActionMixinKernelSafeProposeAccount: return common.RequestRoleHolder - case common.ActionBitcoinSafeApproveAccount: + case common.ActionBitcoinSafeApproveAccount, common.ActionMixinKernelSafeApproveAccount: return common.RequestRoleObserver - case common.ActionBitcoinSafeProposeTransaction: + case common.ActionBitcoinSafeProposeTransaction, common.ActionMixinKernelSafeProposeTransaction: return common.RequestRoleHolder - case common.ActionBitcoinSafeApproveTransaction: + case common.ActionBitcoinSafeApproveTransaction, common.ActionMixinKernelSafeApproveTransaction: return common.RequestRoleObserver - case common.ActionBitcoinSafeRevokeTransaction: + case common.ActionBitcoinSafeRevokeTransaction, common.ActionMixinKernelSafeRevokeTransaction: return common.RequestRoleObserver - case common.ActionBitcoinSafeCloseAccount: + case common.ActionBitcoinSafeCloseAccount, common.ActionMixinKernelSafeCloseAccount: return common.RequestRoleObserver default: return 0 @@ -223,6 +230,18 @@ func (node *Node) processRequest(ctx context.Context, req *common.Request) error return node.processBitcoinSafeRevokeTransaction(ctx, req) case common.ActionBitcoinSafeCloseAccount: return node.processBitcoinSafeCloseAccount(ctx, req) + case common.ActionMixinKernelSafeProposeAccount: + return node.processMixinKernelSafeProposeAccount(ctx, req) + case common.ActionMixinKernelSafeApproveAccount: + return node.processMixinKernelSafeApproveAccount(ctx, req) + case common.ActionMixinKernelSafeProposeTransaction: + return node.processMixinKernelSafeProposeTransaction(ctx, req) + case common.ActionMixinKernelSafeApproveTransaction: + return node.processMixinKernelSafeApproveTransaction(ctx, req) + case common.ActionMixinKernelSafeRevokeTransaction: + return node.processMixinKernelSafeRevokeTransaction(ctx, req) + case common.ActionMixinKernelSafeCloseAccount: + return node.processMixinKernelSafeCloseAccount(ctx, req) default: panic(req.Action) } @@ -267,6 +286,12 @@ func (node *Node) processKeyAdd(ctx context.Context, req *common.Request) error if err != nil { return node.store.FailRequest(ctx, req.Id) } + case common.CurveEdwards25519Mixin: + err = mixin.VerifyPublicKey(req.Holder) + logger.Printf("mixin.VerifyPublicKey(%s, %x) => %v", req.Holder, chainCode, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } default: panic(req.Curve) } @@ -299,6 +324,8 @@ func (node *Node) processSignerSignatureResponse(ctx context.Context, req *commo switch safe.Chain { case SafeChainBitcoin, SafeChainLitecoin: return node.processBitcoinSafeSignatureResponse(ctx, req) + case SafeChainMixinKernel: + return node.processMixinKernelSafeSignatureResponse(ctx, req) default: panic(safe.Chain) } diff --git a/keeper/keeper_test.go b/keeper/keeper_test.go index 2dde1488..a8175f6a 100644 --- a/keeper/keeper_test.go +++ b/keeper/keeper_test.go @@ -31,7 +31,6 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/fox-one/mixin-sdk-go" "github.com/gofrs/uuid/v5" "github.com/pelletier/go-toml" "github.com/shopspring/decimal" @@ -127,7 +126,7 @@ func TestKeeper(t *testing.T) { pendings, err = node.store.ListPendingBitcoinUTXOsForHolder(ctx, holder) require.Nil(err) require.Len(pendings, 0) - testSpareKeys(ctx, require, node, 0, 0, 0) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveSecp256k1ECDSABitcoin) testAccountantSpentTransaction(ctx, require, signedRaw, testHolderSigner) } @@ -173,7 +172,7 @@ func TestKeeperCloseAccountWithSignerObserver(t *testing.T) { require.Nil(err) require.Len(pendings, 0) - testSpareKeys(ctx, require, node, 0, 0, 0) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveSecp256k1ECDSABitcoin) testAccountantSpentTransaction(ctx, require, signedRaw, testSignerObserver) @@ -222,7 +221,7 @@ func TestKeeperCloseAccountWithHolderObserver(t *testing.T) { require.Nil(err) require.Len(pendings, 0) - testSpareKeys(ctx, require, node, 0, 0, 0) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveSecp256k1ECDSABitcoin) testAccountantSpentTransaction(ctx, require, signedRaw, testHolderObserver) @@ -249,38 +248,38 @@ func testPrepare(require *require.Assertions) (context.Context, *Node, string, [ timestamp, err := node.timestamp(ctx) require.Nil(err) require.Equal(time.Unix(0, node.conf.MTG.Genesis.Timestamp), timestamp) - testSpareKeys(ctx, require, node, 0, 0, 0) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveSecp256k1ECDSABitcoin) id := uuid.Must(uuid.NewV4()).String() extra := append([]byte{common.RequestRoleSigner}, chainCode...) extra = append(extra, common.RequestFlagNone) - out := testBuildSignerOutput(node, id, mpc, common.OperationTypeKeygenOutput, extra) + out := testBuildSignerOutput(node, id, mpc, common.OperationTypeKeygenOutput, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) v, err := node.store.ReadProperty(ctx, id) require.Nil(err) require.Equal("", v) - testSpareKeys(ctx, require, node, 0, 1, 0) + testSpareKeys(ctx, require, node, 0, 1, 0, common.CurveSecp256k1ECDSABitcoin) id = uuid.Must(uuid.NewV4()).String() observer := testPublicKey(testBitcoinKeyObserverPrivate) occ := common.DecodeHexOrPanic(testBitcoinKeyObserverChainCode) extra = append([]byte{common.RequestRoleObserver}, occ...) extra = append(extra, common.RequestFlagNone) - out = testBuildObserverRequest(node, id, observer, common.ActionObserverAddKey, extra) + out = testBuildObserverRequest(node, id, observer, common.ActionObserverAddKey, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) v, err = node.store.ReadProperty(ctx, id) require.Nil(err) require.Equal("", v) - testSpareKeys(ctx, require, node, 0, 1, 1) + testSpareKeys(ctx, require, node, 0, 1, 1, common.CurveSecp256k1ECDSABitcoin) batch := byte(64) id = uuid.Must(uuid.NewV4()).String() dummy := testPublicKey(testBitcoinKeyDummyHolderPrivate) - out = testBuildObserverRequest(node, id, dummy, common.ActionObserverRequestSignerKeys, []byte{batch}) + out = testBuildObserverRequest(node, id, dummy, common.ActionObserverRequestSignerKeys, []byte{batch}, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) for i := byte(0); i < batch; i++ { - pid := mixin.UniqueConversationID(id, fmt.Sprintf("%8d", i)) - pid = mixin.UniqueConversationID(pid, fmt.Sprintf("MTG:%v:%d", node.signer.Genesis.Members, node.signer.Genesis.Threshold)) + pid := common.UniqueId(id, fmt.Sprintf("%8d", i)) + pid = common.UniqueId(pid, fmt.Sprintf("MTG:%v:%d", node.signer.Genesis.Members, node.signer.Genesis.Threshold)) v, _ := node.store.ReadProperty(ctx, pid) var om map[string]any json.Unmarshal([]byte(v), &om) @@ -290,15 +289,15 @@ func testPrepare(require *require.Assertions) (context.Context, *Node, string, [ require.Nil(err) require.Equal(pid, o.Id) } - testSpareKeys(ctx, require, node, 0, 1, 1) + testSpareKeys(ctx, require, node, 0, 1, 1, common.CurveSecp256k1ECDSABitcoin) for i := 0; i < 10; i++ { testUpdateAccountPrice(ctx, require, node) } rid, publicKey := testSafeProposeAccount(ctx, require, node, mpc, observer) - testSpareKeys(ctx, require, node, 0, 0, 0) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveSecp256k1ECDSABitcoin) testSafeApproveAccount(ctx, require, node, mpc, observer, rid, publicKey) - testSpareKeys(ctx, require, node, 0, 0, 0) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveSecp256k1ECDSABitcoin) for i := 0; i < 10; i++ { testUpdateNetworkStatus(ctx, require, node, 793574, "00000000000000000002a4f5cd899ea457314c808897c5c5f1f1cd6ffe2b266a") } @@ -314,7 +313,7 @@ func testUpdateAccountPrice(ctx context.Context, require *require.Assertions, no extra = binary.BigEndian.AppendUint64(extra, testAccountPriceAmount*100000000) extra = binary.BigEndian.AppendUint64(extra, 10000) dummy := testPublicKey(testBitcoinKeyDummyHolderPrivate) - out := testBuildObserverRequest(node, id, dummy, common.ActionObserverSetOperationParams, extra) + out := testBuildObserverRequest(node, id, dummy, common.ActionObserverSetOperationParams, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) plan, err := node.store.ReadLatestOperationParams(ctx, SafeChainBitcoin, time.Now()) @@ -334,7 +333,7 @@ func testUpdateNetworkStatus(ctx context.Context, require *require.Assertions, n extra = binary.BigEndian.AppendUint64(extra, height) extra = append(extra, hash[:]...) dummy := testPublicKey(testBitcoinKeyDummyHolderPrivate) - out := testBuildObserverRequest(node, id, dummy, common.ActionObserverUpdateNetworkStatus, extra) + out := testBuildObserverRequest(node, id, dummy, common.ActionObserverUpdateNetworkStatus, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) info, err := node.store.ReadLatestNetworkInfo(ctx, SafeChainBitcoin, time.Now()) @@ -366,7 +365,7 @@ func testSafeRevokeTransaction(ctx context.Context, require *require.Assertions, extra := uuid.Must(uuid.FromString(tx.RequestId)).Bytes() extra = append(extra, sig...) - out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeRevokeTransaction, extra) + out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeRevokeTransaction, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) requests, err := node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) require.Nil(err) @@ -420,7 +419,7 @@ func testSafeApproveTransaction(ctx context.Context, require *require.Assertions extra := uuid.Must(uuid.FromString(tx.RequestId)).Bytes() extra = append(extra, ref[:]...) - out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeApproveTransaction, extra) + out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeApproveTransaction, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) requests, err := node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) require.Nil(err) @@ -429,9 +428,9 @@ func testSafeApproveTransaction(ctx context.Context, require *require.Assertions require.Equal(common.RequestStatePending, tx.State) msg, _ := hex.DecodeString(requests[0].Message) - out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignInput, msg) - op := signer.TestCMPProcessOutput(ctx, require, signers, out, requests[0].RequestId) - out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignInput, msg, common.CurveSecp256k1ECDSABitcoin) + op := signer.TestProcessOutput(ctx, require, signers, out, requests[0].RequestId) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStatePending) require.Len(requests, 1) @@ -441,9 +440,9 @@ func testSafeApproveTransaction(ctx context.Context, require *require.Assertions require.Equal(common.RequestStatePending, tx.State) msg, _ = hex.DecodeString(requests[1].Message) - out = testBuildSignerOutput(node, requests[1].RequestId, safe.Signer, common.OperationTypeSignInput, msg) - op = signer.TestCMPProcessOutput(ctx, require, signers, out, requests[1].RequestId) - out = testBuildSignerOutput(node, requests[1].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra) + out = testBuildSignerOutput(node, requests[1].RequestId, safe.Signer, common.OperationTypeSignInput, msg, common.CurveSecp256k1ECDSABitcoin) + op = signer.TestProcessOutput(ctx, require, signers, out, requests[1].RequestId) + out = testBuildSignerOutput(node, requests[1].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) require.Len(requests, 0) @@ -461,7 +460,7 @@ func testSafeApproveTransaction(ctx context.Context, require *require.Assertions } mb := common.DecodeHexOrPanic(tx.RawTransaction) exk := crypto.Blake3Hash([]byte(common.Base91Encode(mb))) - rid := mixin.UniqueConversationID(transactionHash, hex.EncodeToString(exk[:])) + rid := common.UniqueId(transactionHash, hex.EncodeToString(exk[:])) b := testReadObserverResponse(ctx, require, node, rid, common.ActionBitcoinSafeApproveTransaction) require.Equal(mb, b) @@ -625,7 +624,7 @@ func testSafeCloseAccount(ctx context.Context, require *require.Assertions, node extra := uuid.Must(uuid.FromString(tx.RequestId)).Bytes() extra = append(extra, ref[:]...) - out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeCloseAccount, extra) + out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeCloseAccount, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) requests, err := node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) require.Nil(err) @@ -634,9 +633,9 @@ func testSafeCloseAccount(ctx context.Context, require *require.Assertions, node require.Equal(common.RequestStatePending, tx.State) msg, _ := hex.DecodeString(requests[0].Message) - out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignInput, msg) - op := signer.TestCMPProcessOutput(ctx, require, signers, out, requests[0].RequestId) - out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignInput, msg, common.CurveSecp256k1ECDSABitcoin) + op := signer.TestProcessOutput(ctx, require, signers, out, requests[0].RequestId) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStatePending) require.Len(requests, 0) @@ -654,7 +653,7 @@ func testSafeCloseAccount(ctx context.Context, require *require.Assertions, node } mb := common.DecodeHexOrPanic(tx.RawTransaction) exk := crypto.Blake3Hash([]byte(common.Base91Encode(mb))) - rid := mixin.UniqueConversationID(transactionHash, hex.EncodeToString(exk[:])) + rid := common.UniqueId(transactionHash, hex.EncodeToString(exk[:])) b := testReadObserverResponse(ctx, require, node, rid, common.ActionBitcoinSafeApproveTransaction) require.Equal(mb, b) @@ -692,7 +691,7 @@ func testSafeCloseAccount(ctx context.Context, require *require.Assertions, node extra := uuid.Nil.Bytes() extra = append(extra, ref[:]...) id := uuid.FromBytesOrNil(msgTx.TxOut[1].PkScript[2:]).String() - out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeCloseAccount, extra) + out := testBuildObserverRequest(node, id, testPublicKey(testBitcoinKeyHolderPrivate), common.ActionBitcoinSafeCloseAccount, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) pendings, err = node.store.ListPendingBitcoinUTXOsForHolder(ctx, safe.Holder) @@ -721,12 +720,12 @@ func testObserverHolderDeposit(ctx context.Context, require *require.Assertions, holder := testPublicKey(testBitcoinKeyHolderPrivate) wsa, _ := node.buildBitcoinWitnessAccountWithDerivation(ctx, holder, signer, observer, bitcoinDefaultDerivationPath(), testTimelockDuration, SafeChainBitcoin) - out := testBuildObserverRequest(node, id, holder, common.ActionObserverHolderDeposit, extra) + out := testBuildObserverRequest(node, id, holder, common.ActionObserverHolderDeposit, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) mainInputs, err := node.store.ListAllBitcoinUTXOsForHolder(ctx, holder) require.Nil(err) - require.Len(mainInputs, 1*t) + require.Len(mainInputs, t) utxo := mainInputs[t-1] require.Equal(uint32(input.Index), utxo.Index) require.Equal(input.Satoshi, utxo.Satoshi) @@ -745,6 +744,7 @@ func testSafeProposeAccount(ctx context.Context, require *require.Assertions, no testStep(ctx, require, node, out) b := testReadObserverResponse(ctx, require, node, id, common.ActionBitcoinSafeProposeAccount) wsa, err := bitcoin.UnmarshalWitnessScriptAccount(b) + require.Nil(err) require.Equal(testSafeAddress, wsa.Address) require.Equal(uint32(6), wsa.Sequence) @@ -775,7 +775,7 @@ func testSafeApproveAccount(ctx context.Context, require *require.Assertions, no signature := ecdsa.Sign(hp, hash) extra := uuid.FromStringOrNil(rid).Bytes() extra = append(extra, signature.Serialize()...) - out := testBuildObserverRequest(node, id, holder, common.ActionBitcoinSafeApproveAccount, extra) + out := testBuildObserverRequest(node, id, holder, common.ActionBitcoinSafeApproveAccount, extra, common.CurveSecp256k1ECDSABitcoin) testStep(ctx, require, node, out) b := testReadObserverResponse(ctx, require, node, id, common.ActionBitcoinSafeApproveAccount) wsa, err := bitcoin.UnmarshalWitnessScriptAccount(b) @@ -814,13 +814,13 @@ func testStep(ctx context.Context, require *require.Assertions, node *Node, out require.Nil(req) } -func testSpareKeys(ctx context.Context, require *require.Assertions, node *Node, hc, sc, oc int) { +func testSpareKeys(ctx context.Context, require *require.Assertions, node *Node, hc, sc, oc int, crv byte) { for r, c := range map[int]int{ common.RequestRoleHolder: hc, common.RequestRoleSigner: sc, common.RequestRoleObserver: oc, } { - skc, err := node.store.CountSpareKeys(ctx, common.CurveSecp256k1ECDSABitcoin, common.RequestFlagNone, r) + skc, err := node.store.CountSpareKeys(ctx, crv, common.RequestFlagNone, r) require.Nil(err) require.Equal(c, skc) } @@ -835,6 +835,10 @@ func testReadObserverResponse(ctx context.Context, require *require.Assertions, params, _ := node.store.ReadLatestOperationParams(ctx, SafeChainBitcoin, time.Now()) require.Equal(params.OperationPriceAsset, om["asset_id"]) require.Equal(params.OperationPriceAmount.String(), om["amount"]) + } else if typ == common.ActionMixinKernelSafeApproveAccount { + params, _ := node.store.ReadLatestOperationParams(ctx, SafeChainMixinKernel, time.Now()) + require.Equal(params.OperationPriceAsset, om["asset_id"]) + require.Equal(params.OperationPriceAmount.String(), om["amount"]) } else { require.Equal(node.conf.ObserverAssetId, om["asset_id"]) require.Equal("1", om["amount"]) @@ -853,10 +857,16 @@ func testReadObserverResponse(ctx context.Context, require *require.Assertions, } func testBuildHolderRequest(node *Node, id, public string, action byte, assetId string, extra []byte, amount decimal.Decimal) *mtg.Output { + crv := byte(common.CurveSecp256k1ECDSABitcoin) + switch action { + case common.ActionBitcoinSafeProposeAccount, common.ActionBitcoinSafeProposeTransaction: + case common.ActionMixinKernelSafeProposeAccount, common.ActionMixinKernelSafeProposeTransaction: + crv = common.CurveEdwards25519Mixin + } op := &common.Operation{ Id: id, Type: action, - Curve: common.CurveSecp256k1ECDSABitcoin, + Curve: crv, Public: public, Extra: extra, } @@ -871,11 +881,11 @@ func testBuildHolderRequest(node *Node, id, public string, action byte, assetId } } -func testBuildObserverRequest(node *Node, id, public string, action byte, extra []byte) *mtg.Output { +func testBuildObserverRequest(node *Node, id, public string, action byte, extra []byte, crv byte) *mtg.Output { op := &common.Operation{ Id: id, Type: action, - Curve: common.CurveSecp256k1ECDSABitcoin, + Curve: crv, Public: public, Extra: extra, } @@ -895,11 +905,19 @@ func testBuildObserverRequest(node *Node, id, public string, action byte, extra } } -func testBuildSignerOutput(node *Node, id, public string, action byte, extra []byte) *mtg.Output { +func testBuildSignerOutput(node *Node, id, public string, action byte, extra []byte, crv byte) *mtg.Output { + path := bitcoinDefaultDerivationPath() + switch crv { + case common.CurveSecp256k1ECDSABitcoin: + case common.CurveEdwards25519Mixin: + path = mixinDefaultDerivationPath() + default: + panic(crv) + } op := &common.Operation{ Id: id, Type: action, - Curve: common.CurveSecp256k1ECDSABitcoin, + Curve: crv, Extra: extra, } timestamp := time.Now() @@ -907,7 +925,7 @@ func testBuildSignerOutput(node *Node, id, public string, action byte, extra []b case common.OperationTypeKeygenInput: op.Public = hex.EncodeToString(common.Fingerprint(public)) case common.OperationTypeSignInput: - fingerPath := append(common.Fingerprint(public), bitcoinDefaultDerivationPath()...) + fingerPath := append(common.Fingerprint(public), path...) op.Public = hex.EncodeToString(fingerPath) case common.OperationTypeKeygenOutput: op.Public = public @@ -974,8 +992,8 @@ func testRecipient() []byte { return append(extra, id.Bytes()...) } -func testPublicKey(pub string) string { - seed, _ := hex.DecodeString(pub) +func testPublicKey(priv string) string { + seed, _ := hex.DecodeString(priv) _, dk := btcec.PrivKeyFromBytes(seed) return hex.EncodeToString(dk.SerializeCompressed()) } diff --git a/keeper/mixin.go b/keeper/mixin.go index 5fc6ac2d..9410f1ce 100644 --- a/keeper/mixin.go +++ b/keeper/mixin.go @@ -1,6 +1,730 @@ package keeper +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/mixin/logger" + "github.com/MixinNetwork/safe/apps/bitcoin" + "github.com/MixinNetwork/safe/apps/mixin" + "github.com/MixinNetwork/safe/common" + "github.com/MixinNetwork/safe/common/abi" + "github.com/MixinNetwork/safe/keeper/store" + "github.com/gofrs/uuid/v5" + "github.com/shopspring/decimal" +) + +// We will always only allow XIN deposit for mixin kernel, because this is the only use case. +// But all code should imply more assets may come in the future. We make this decision, because +// the native chain safe is feasible for all other assets. +func (node *Node) doMixinKernelHolderDeposit(ctx context.Context, req *common.Request, deposit *Deposit, safe *store.Safe, bondId string, minimum decimal.Decimal) error { + if deposit.Asset != SafeMixinKernelAssetId { + return node.store.FailRequest(ctx, req.Id) + } + old, _, err := node.store.ReadMixinKernelUTXO(ctx, deposit.Hash, int(deposit.Index)) + logger.Printf("store.ReadMixinKernelUTXO(%s, %d) => %v %v", deposit.Hash, deposit.Index, old, err) + if err != nil { + return fmt.Errorf("store.ReadMixinKernelUTXO(%s, %d) => %v", deposit.Hash, deposit.Index, err) + } else if old != nil { + return node.store.FailRequest(ctx, req.Id) + } + + dh, err := crypto.HashFromString(deposit.Hash) + if err != nil { + panic(err) + } + mtx, err := common.ReadKernelTransaction(node.conf.MixinRPC, dh) + if err != nil { + return fmt.Errorf("common.ReadKernelTransaction(%s) => %v", deposit.Hash, err) + } + + amount := decimal.NewFromBigInt(deposit.Amount, -mixin.ValuePrecision) + change, err := node.checkMixinKernelChange(ctx, deposit, mtx) + logger.Printf("node.checkMixinKernelChange(%v, %v) => %t %v", deposit, mtx, change, err) + if err != nil { + return fmt.Errorf("node.checkMixinKernelChange(%v) => %v", deposit, err) + } + if amount.Cmp(minimum) < 0 && !change { + return node.store.FailRequest(ctx, req.Id) + } + if amount.Cmp(decimal.New(mixin.ValueDust, -mixin.ValuePrecision)) < 0 { + panic(deposit.Hash) + } + + output, err := node.verifyMixinKernelTransaction(ctx, req, deposit, mtx, safe) + logger.Printf("node.verifyMixinKernelTransaction(%v) => %v %v", req, output, err) + if err != nil { + return fmt.Errorf("node.verifyMixinKernelTransaction(%s) => %v", deposit.Hash, err) + } + if output == nil { + return node.store.FailRequest(ctx, req.Id) + } + + if !change { + err = node.buildTransaction(ctx, bondId, safe.Receivers, int(safe.Threshold), amount.String(), nil, req.Id) + if err != nil { + return fmt.Errorf("node.buildTransaction(%v) => %v", req, err) + } + } + + return node.store.WriteMixinKernelOutputFromRequest(ctx, safe.Address, deposit.Asset, output, req) +} + // holder key just for safe verification, not for kernel -// observer holds the view key, i.e. the accountant key -// no recovery key needed, and signer holds spend key -// the risk here is the accountant key loss +// the kernel view key is derived by hash of holder, signer and observer +// then keeper and observer must never disclose the observer public key +// thus the kernel view key remains anonymous from public +func (node *Node) processMixinKernelSafeProposeAccount(ctx context.Context, req *common.Request) error { + if req.Role != common.RequestRoleHolder { + panic(req.Role) + } + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Curve) + } + chain := byte(SafeChainMixinKernel) + rce, err := hex.DecodeString(req.Extra) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + ver, _ := common.ReadKernelTransaction(node.conf.MixinRPC, req.MixinHash) + if len(rce) == 32 && len(ver.References) == 1 && ver.References[0].String() == req.Extra { + stx, _ := common.ReadKernelTransaction(node.conf.MixinRPC, ver.References[0]) + rce = common.DecodeMixinObjectExtra(stx.Extra) + } + arp, err := req.ParseMixinRecipient(rce) + logger.Printf("req.ParseMixinRecipient(%v) => %v %v", req, arp, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + + plan, err := node.store.ReadLatestOperationParams(ctx, chain, req.CreatedAt) + logger.Printf("store.ReadLatestOperationParams(%d) => %v %v", chain, plan, err) + if err != nil { + return fmt.Errorf("node.ReadLatestOperationParams(%d) => %v", chain, err) + } else if plan == nil || !plan.OperationPriceAmount.IsPositive() { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + if req.AssetId != plan.OperationPriceAsset { + return node.store.FailRequest(ctx, req.Id) + } + if req.Amount.Cmp(plan.OperationPriceAmount) < 0 { + return node.store.FailRequest(ctx, req.Id) + } + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + return fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err) + } else if safe != nil { + return node.store.FailRequest(ctx, req.Id) + } + old, err := node.store.ReadSafeProposal(ctx, req.Id) + if err != nil { + return fmt.Errorf("store.ReadSafeProposal(%s) => %v", req.Id, err) + } else if old != nil { + return node.store.FailRequest(ctx, req.Id) + } + + signer, observer, err := node.store.AssignSignerAndObserverToHolder(ctx, req, SafeKeyBackupMaturity, arp.Observer) + logger.Printf("store.AssignSignerAndObserverToHolder(%s) => %s %s %v", req.Holder, signer, observer, err) + if err != nil { + return fmt.Errorf("store.AssignSignerAndObserverToHolder(%v) => %v", req, err) + } + if signer == "" || observer == "" { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + if arp.Observer != "" && arp.Observer != observer { + return fmt.Errorf("store.AssignSignerAndObserverToHolder(%v) => %v %s", req, arp, observer) + } + if !common.CheckUnique(req.Holder, signer, observer) { + return node.refundAndFailRequest(ctx, req, arp.Receivers, int(arp.Threshold)) + } + + acc := mixin.BuildAddress(req.Holder, signer, observer) + logger.Verbosef("mixin.BuildAddress(%v) => %v", req, acc) + old, err = node.store.ReadSafeProposalByAddress(ctx, acc.String()) + if err != nil { + return fmt.Errorf("store.ReadSafeProposalByAddress(%s) => %v", acc.String(), err) + } else if old != nil { + return node.store.FailRequest(ctx, req.Id) + } + + exk := node.writeStorageOrPanic(ctx, []byte(common.Base91Encode([]byte(acc.String())))) + typ := byte(common.ActionMixinKernelSafeProposeAccount) + err = node.sendObserverResponseWithReferences(ctx, req.Id, typ, req.Curve, exk) + if err != nil { + return fmt.Errorf("node.sendObserverResponse(%s, %x) => %v", req.Id, exk, err) + } + + path := mixinDefaultDerivationPath() + sp := &store.SafeProposal{ + RequestId: req.Id, + Chain: chain, + Holder: req.Holder, + Signer: signer, + Observer: observer, + Timelock: arp.Timelock, + Path: hex.EncodeToString(path), + Address: acc.String(), + Extra: acc.PrivateViewKey[:], + Receivers: arp.Receivers, + Threshold: arp.Threshold, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + return node.store.WriteSafeProposalWithRequest(ctx, sp) +} + +func (node *Node) processMixinKernelSafeApproveAccount(ctx context.Context, req *common.Request) error { + if req.Role != common.RequestRoleObserver { + panic(req.Role) + } + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Curve) + } + chain := byte(SafeChainMixinKernel) + old, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + return fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err) + } else if old != nil { + return node.store.FailRequest(ctx, req.Id) + } + + extra, _ := hex.DecodeString(req.Extra) + if len(extra) != 16+64 { + return node.store.FailRequest(ctx, req.Id) + } + rid, err := uuid.FromBytes(extra[:16]) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + sp, err := node.store.ReadSafeProposal(ctx, rid.String()) + if err != nil { + return fmt.Errorf("store.ReadSafeProposal(%v) => %s %v", req, rid.String(), err) + } else if sp == nil { + return node.store.FailRequest(ctx, req.Id) + } else if sp.Holder != req.Holder { + return node.store.FailRequest(ctx, req.Id) + } else if sp.Chain != chain { + return node.store.FailRequest(ctx, req.Id) + } + + ms := fmt.Sprintf("APPROVE:%s:%s", rid.String(), sp.Address) + msg := mixin.HashMessageForSignature(ms) + err = mixin.VerifySignature(req.Holder, msg, extra[16:]) + logger.Printf("mixin.VerifySignature(%v) => %v", req, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + + spr, err := node.store.ReadRequest(ctx, sp.RequestId) + if err != nil { + return fmt.Errorf("store.ReadRequest(%s) => %v", sp.RequestId, err) + } + exk := node.writeStorageOrPanic(ctx, []byte(common.Base91Encode([]byte(sp.Address)))) + typ := byte(common.ActionMixinKernelSafeApproveAccount) + err = node.sendObserverResponseWithAssetAndReferences(ctx, req.Id, typ, req.Curve, spr.AssetId, spr.Amount.String(), exk) + if err != nil { + return fmt.Errorf("node.sendObserverResponse(%s, %x) => %v", req.Id, exk, err) + } + + safe := &store.Safe{ + Holder: sp.Holder, + Chain: sp.Chain, + Signer: sp.Signer, + Observer: sp.Observer, + Timelock: sp.Timelock, + Path: sp.Path, + Address: sp.Address, + Extra: sp.Extra, + Receivers: sp.Receivers, + Threshold: sp.Threshold, + RequestId: req.Id, + State: SafeStateApproved, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + return node.store.WriteSafeWithRequest(ctx, safe) +} + +func (node *Node) processMixinKernelSafeCloseAccount(ctx context.Context, req *common.Request) error { + if req.Role != common.RequestRoleObserver { + panic(req.Role) + } + panic(0) +} + +func (node *Node) processMixinKernelSafeProposeTransaction(ctx context.Context, req *common.Request) error { + if req.Role != common.RequestRoleHolder { + panic(req.Role) + } + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Curve) + } + chain := byte(SafeChainMixinKernel) + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + return fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err) + } + if safe == nil || safe.Chain != chain { + return node.store.FailRequest(ctx, req.Id) + } + if safe.State != SafeStateApproved { + return node.store.FailRequest(ctx, req.Id) + } + + meta, err := node.fetchAssetMeta(ctx, req.AssetId) + logger.Printf("node.fetchAssetMeta(%s) => %v %v", req.AssetId, meta, err) + if err != nil { + return fmt.Errorf("node.fetchAssetMeta(%s) => %v", req.AssetId, err) + } + if meta.Chain != SafeChainMVM { + return node.store.FailRequest(ctx, req.Id) + } + deployed, err := abi.CheckFactoryAssetDeployed(node.conf.MVMRPC, meta.AssetKey) + logger.Printf("abi.CheckFactoryAssetDeployed(%s) => %v %v", meta.AssetKey, deployed, err) + if err != nil { + return fmt.Errorf("api.CheckFatoryAssetDeployed(%s) => %v", meta.AssetKey, err) + } + if deployed.Sign() <= 0 { + return node.store.FailRequest(ctx, req.Id) + } + assetId := uuid.Must(uuid.FromBytes(deployed.Bytes())) + + plan, err := node.store.ReadLatestOperationParams(ctx, safe.Chain, req.CreatedAt) + logger.Printf("store.ReadLatestOperationParams(%d) => %v %v", safe.Chain, plan, err) + if err != nil { + return fmt.Errorf("store.ReadLatestOperationParams(%d) => %v", safe.Chain, err) + } else if plan == nil || !plan.TransactionMinimum.IsPositive() { + return node.refundAndFailRequest(ctx, req, safe.Receivers, int(safe.Threshold)) + } + if req.Amount.Cmp(plan.TransactionMinimum) < 0 { + return node.store.FailRequest(ctx, req.Id) + } + + bondId, _, err := node.getBondAsset(ctx, assetId.String(), req.Holder) + logger.Printf("node.getBondAsset(%s, %s) => %s %v", assetId.String(), req.Holder, bondId, err) + if err != nil { + return fmt.Errorf("node.getBondAsset(%s, %s) => %v", assetId.String(), req.Holder, err) + } + if crypto.NewHash([]byte(req.AssetId)) != bondId { + return node.store.FailRequest(ctx, req.Id) + } + + extra, _ := hex.DecodeString(req.Extra) + if len(extra) < 33 { + return node.store.FailRequest(ctx, req.Id) + } + + switch extra[0] { + case common.FlagProposeNormalTransaction: + case common.FlagProposeRecoveryTransaction: + default: + return node.store.FailRequest(ctx, req.Id) + } + extra = extra[1:] + + var outputs []*mixin.Output + ver, _ := common.ReadKernelTransaction(node.conf.MixinRPC, req.MixinHash) + if len(extra) == 32 && len(ver.References) == 1 && ver.References[0].String() == hex.EncodeToString(extra) { + stx, _ := common.ReadKernelTransaction(node.conf.MixinRPC, ver.References[0]) + extra := common.DecodeMixinObjectExtra(stx.Extra) + var recipients [][2]string // TODO better encoding + err = json.Unmarshal(extra, &recipients) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + var total decimal.Decimal + for _, rp := range recipients { + addr, err := mixin.ParseAddress(rp[0]) + logger.Printf("mixin.ParseAddress(%s) => %v %v", string(extra), addr, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + amt, err := decimal.NewFromString(rp[1]) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + if amt.Cmp(plan.TransactionMinimum) < 0 { + return node.store.FailRequest(ctx, req.Id) + } + total = total.Add(amt) + outputs = append(outputs, &mixin.Output{ + Address: addr, + Amount: amt, + }) + } + if !total.Equal(req.Amount) { + return node.store.FailRequest(ctx, req.Id) + } + } else { + addr, err := mixin.ParseAddress(string(extra[16:])) + logger.Printf("mixin.ParseAddress(%s) => %v %v", string(extra), addr, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + outputs = append(outputs, &mixin.Output{ + Address: addr, + Amount: req.Amount, + }) + } + + allInputs, err := node.store.ListAllMixinKernelUTXOsForHolderAndAsset(ctx, safe.Holder, assetId.String()) + if err != nil { + return fmt.Errorf("store.ListAllMixinKernelUTXOsForHolderAndAsset(%s, %s) => %v", req.Holder, assetId.String(), err) + } + psbt, err := mixin.BuildPartiallySignedTransaction(allInputs, outputs, req.Id, safe.Holder, safe.Signer, safe.Observer) + logger.Printf("mixin.BuildPartiallySignedTransaction(%v) => %v %v", req, psbt, err) + if bitcoin.IsInsufficientInputError(err) { + return node.refundAndFailRequest(ctx, req, safe.Receivers, int(safe.Threshold)) + } + if err != nil { + return fmt.Errorf("mixin.BuildPartiallySignedTransaction(%v) => %v", req, err) + } + + msg := common.Base91Encode(psbt.PayloadMarshal()) + exk := node.writeStorageOrPanic(ctx, []byte(msg)) + typ := byte(common.ActionMixinKernelSafeProposeTransaction) + err = node.sendObserverResponseWithReferences(ctx, req.Id, typ, req.Curve, exk) + if err != nil { + return fmt.Errorf("node.sendObserverResponse(%s, %x) => %v", req.Id, exk, err) + } + + total := decimal.Zero + recipients := make([]map[string]string, len(outputs)) + for i, out := range outputs { + recipients[i] = map[string]string{ + "receiver": out.Address.String(), + "amount": out.Amount.String(), + } + total = total.Add(out.Amount) + } + if !total.Equal(req.Amount) { + return node.store.FailRequest(ctx, req.Id) + } + data := common.MarshalJSONOrPanic(map[string]any{ + "recipients": recipients, + "storage": exk, + }) + tx := &store.Transaction{ + TransactionHash: psbt.PayloadHash().String(), + RawTransaction: hex.EncodeToString(psbt.PayloadMarshal()), + Holder: req.Holder, + Chain: safe.Chain, + AssetId: assetId.String(), + State: common.RequestStateInitial, + Data: string(data), + RequestId: req.Id, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + inputs := make([]*store.TransactionInput, len(psbt.Inputs)) + for i, in := range psbt.Inputs { + inputs[i] = &store.TransactionInput{ + Hash: in.Hash.String(), + Index: uint32(in.Index), + } + } + return node.store.WriteTransactionWithRequest(ctx, tx, inputs) +} + +func (node *Node) processMixinKernelSafeRevokeTransaction(ctx context.Context, req *common.Request) error { + if req.Role != common.RequestRoleObserver { + panic(req.Role) + } + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Curve) + } + chain := byte(SafeChainMixinKernel) + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + return fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err) + } + if safe == nil || safe.Chain != chain { + return node.store.FailRequest(ctx, req.Id) + } + + extra, _ := hex.DecodeString(req.Extra) + if len(extra) < 64 { + return node.store.FailRequest(ctx, req.Id) + } + rid, err := uuid.FromBytes(extra[:16]) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + tx, err := node.store.ReadTransactionByRequestId(ctx, rid.String()) + if err != nil { + return fmt.Errorf("store.ReadTransactionByRequestId(%v) => %s %v", req, rid.String(), err) + } else if tx == nil { + return node.store.FailRequest(ctx, req.Id) + } else if tx.Holder != req.Holder { + return node.store.FailRequest(ctx, req.Id) + } else if tx.State != common.RequestStateInitial { + return node.store.FailRequest(ctx, req.Id) + } + + ms := fmt.Sprintf("REVOKE:%s:%s", rid.String(), tx.TransactionHash) + msg := mixin.HashMessageForSignature(ms) + err = mixin.VerifySignature(req.Holder, msg, extra[16:]) + logger.Printf("holder: mixin.VerifySignature(%v) => %v", req, err) + if err != nil { + err = mixin.VerifySignature(safe.Observer, msg, extra[16:]) + logger.Printf("observer: mixin.VerifySignature(%v) => %v", req, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + } + + bondId, _, err := node.getBondAsset(ctx, tx.AssetId, safe.Holder) + logger.Printf("node.getBondAsset(%s, %s) => %s %v", tx.AssetId, req.Holder, bondId, err) + if err != nil { + return fmt.Errorf("node.getBondAsset(%s, %s) => %v", tx.AssetId, req.Holder, err) + } + var data struct { + Recipients []map[string]string `json:"recipients"` + } + err = json.Unmarshal([]byte(tx.Data), &data) + if err != nil { + panic(err) + } + amount := decimal.Zero + for _, t := range data.Recipients { + ta := decimal.RequireFromString(t["amount"]) + if ta.Cmp(decimal.NewFromFloat(0.0001)) < 0 { + panic(tx.Data) + } + amount = amount.Add(ta) + } + meta, err := node.fetchAssetMeta(ctx, bondId.String()) + logger.Printf("node.fetchAssetMeta(%s) => %v %v", bondId.String(), meta, err) + if err != nil { + return fmt.Errorf("node.fetchAssetMeta(%s) => %v", bondId.String(), err) + } + if meta.Chain != SafeChainMVM { + return node.store.FailRequest(ctx, req.Id) + } + err = node.buildTransaction(ctx, meta.AssetId, safe.Receivers, int(safe.Threshold), amount.String(), nil, req.Id) + if err != nil { + return err + } + + return node.store.RevokeTransactionWithRequest(ctx, tx, safe, req) +} + +func (node *Node) processMixinKernelSafeApproveTransaction(ctx context.Context, req *common.Request) error { + if req.Role != common.RequestRoleObserver { + panic(req.Role) + } + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Curve) + } + chain := byte(SafeChainMixinKernel) + safe, err := node.store.ReadSafe(ctx, req.Holder) + if err != nil { + return fmt.Errorf("store.ReadSafe(%s) => %v", req.Holder, err) + } + if safe == nil || safe.Chain != chain { + return node.store.FailRequest(ctx, req.Id) + } + + extra, _ := hex.DecodeString(req.Extra) + if len(extra) != 16+64 { + return node.store.FailRequest(ctx, req.Id) + } + rid, err := uuid.FromBytes(extra[:16]) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + tx, err := node.store.ReadTransactionByRequestId(ctx, rid.String()) + if err != nil { + return fmt.Errorf("store.ReadTransactionByRequestId(%v) => %s %v", req, rid.String(), err) + } else if tx == nil { + return node.store.FailRequest(ctx, req.Id) + } else if tx.Holder != req.Holder { + return node.store.FailRequest(ctx, req.Id) + } + + ms := fmt.Sprintf("APPROVE:%s:%s", rid.String(), tx.TransactionHash) + msg := mixin.HashMessageForSignature(ms) + err = mixin.VerifySignature(tx.Holder, msg, extra[16:]) + logger.Printf("mixin.VerifySignature(%v) => %v", req, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + + var data struct { + StorageTransaction crypto.Hash `json:"storage"` + } + err = json.Unmarshal([]byte(tx.Data), &data) + if err != nil { + panic(err) + } + + raw := common.DecodeHexOrPanic(tx.RawTransaction) + psbt, err := mixin.ParsePartiallySignedTransaction(raw) + if err != nil { + panic(err) + } + addr := mixin.BuildAddress(safe.Holder, safe.Signer, safe.Observer) + var requests []*store.SignatureRequest + for idx, in := range psbt.Inputs { + pending, err := node.checkTransactionIndexSignaturePending(ctx, tx.TransactionHash, idx, req) + logger.Printf("node.checkTransactionIndexSignaturePending(%s, %d) => %t %v", tx.TransactionHash, idx, pending, err) + if err != nil { + return err + } else if pending { + continue + } + + utxo, _, _ := node.store.ReadMixinKernelUTXO(ctx, in.Hash.String(), in.Index) + r := crypto.KeyMultPubPriv(&utxo.Mask, &addr.PrivateViewKey) + msg := crypto.HashScalar(r, uint64(in.Index)).Bytes() + msg = append(msg, data.StorageTransaction[:]...) + + sr := &store.SignatureRequest{ + TransactionHash: tx.TransactionHash, + InputIndex: idx, + Signer: safe.Signer, + Curve: req.Curve, + Message: hex.EncodeToString(msg), + State: common.RequestStateInitial, + CreatedAt: req.CreatedAt, + UpdatedAt: req.CreatedAt, + } + sr.RequestId = common.UniqueId(req.Id, sr.Message) + requests = append(requests, sr) + } + err = node.store.WriteSignatureRequestsWithRequest(ctx, requests, tx.TransactionHash, req) + logger.Printf("store.WriteSignatureRequestsWithRequest(%s, %d, %v) => %v", tx.TransactionHash, len(requests), req, err) + if err != nil { + return fmt.Errorf("store.WriteSignatureRequestsWithRequest(%s) => %v", tx.TransactionHash, err) + } + + for _, sr := range requests { + err := node.sendSignerSignRequest(ctx, sr, safe.Path) + if err != nil { + return fmt.Errorf("node.sendSignerSignRequest(%v) => %v", sr, err) + } + } + return nil +} + +func (node *Node) processMixinKernelSafeSignatureResponse(ctx context.Context, req *common.Request) error { + if req.Role != common.RequestRoleSigner { + panic(req.Role) + } + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Curve) + } + old, err := node.store.ReadSignatureRequest(ctx, req.Id) + logger.Printf("store.ReadSignatureRequest(%s) => %v %v", req.Id, old, err) + if err != nil { + return fmt.Errorf("store.ReadSignatureRequest(%s) => %v", req.Id, err) + } + if old == nil || old.State == common.RequestStateDone { + return node.store.FailRequest(ctx, req.Id) + } + tx, err := node.store.ReadTransaction(ctx, old.TransactionHash) + if err != nil { + return fmt.Errorf("store.ReadTransaction(%v) => %s %v", req, old.TransactionHash, err) + } + safe, err := node.store.ReadSafe(ctx, tx.Holder) + if err != nil { + return fmt.Errorf("store.ReadSafe(%s) => %v", tx.Holder, err) + } + if safe.Signer != req.Holder { + return node.store.FailRequest(ctx, req.Id) + } + + raw := common.DecodeHexOrPanic(tx.RawTransaction) + sig, _ := hex.DecodeString(req.Extra) + msg := common.DecodeHexOrPanic(old.Message) + spk := mixin.DeriveKey(safe.Signer, msg[:32]) + err = mixin.VerifySignature(spk, raw, sig) + logger.Printf("mixin.VerifySignature(%v) => %v", req, err) + if err != nil { + return node.store.FailRequest(ctx, req.Id) + } + + err = node.store.FinishSignatureRequest(ctx, req) + logger.Printf("store.FinishSignatureRequest(%s) => %v", req.Id, err) + if err != nil { + return fmt.Errorf("store.FinishSignatureRequest(%s) => %v", req.Id, err) + } + + requests, err := node.store.ListAllSignaturesForTransaction(ctx, old.TransactionHash, common.RequestStatePending) + logger.Printf("store.ListAllSignaturesForTransaction(%s) => %d %v", old.TransactionHash, len(requests), err) + if err != nil { + return fmt.Errorf("store.ListAllSignaturesForTransaction(%s) => %v", old.TransactionHash, err) + } + + psbt, err := mixin.ParsePartiallySignedTransaction(raw) + if err != nil { + panic(err) + } + for idx := range psbt.Inputs { + sr := requests[idx] + if sr == nil { + return node.store.FailRequest(ctx, req.Id) + } + msg := common.DecodeHexOrPanic(sr.Message) + sig := common.DecodeHexOrPanic(sr.Signature.String) + spk := mixin.DeriveKey(safe.Signer, msg[:32]) + err = mixin.VerifySignature(spk, raw, sig) + if err != nil { + panic(sr.Signature.String) + } + var msig crypto.Signature + copy(msig[:], sig) + psbt.SignaturesMap = append(psbt.SignaturesMap, map[uint16]*crypto.Signature{0: &msig}) + } + + exk := node.writeStorageOrPanic(ctx, []byte(common.Base91Encode(psbt.Marshal()))) + id := common.UniqueId(old.TransactionHash, hex.EncodeToString(exk[:])) + typ := byte(common.ActionMixinKernelSafeApproveTransaction) + err = node.sendObserverResponseWithReferences(ctx, id, typ, req.Curve, exk) + if err != nil { + return fmt.Errorf("node.sendObserverResponse(%s, %x) => %v", id, exk, err) + } + signed := hex.EncodeToString(psbt.Marshal()) + err = node.store.FinishTransactionSignaturesWithRequest(ctx, old.TransactionHash, signed, req, int64(len(psbt.Inputs)), tx.Chain) + logger.Printf("store.FinishTransactionSignaturesWithRequest(%s, %s, %v) => %v", old.TransactionHash, signed, req, err) + return err +} + +func (node *Node) checkMixinKernelChange(ctx context.Context, deposit *Deposit, mtx *common.VersionedTransaction) (bool, error) { + vin, spentBy, err := node.store.ReadMixinKernelUTXO(ctx, mtx.Inputs[0].Hash.String(), mtx.Inputs[0].Index) + if err != nil || vin == nil { + return false, err + } + tx, err := node.store.ReadTransaction(ctx, spentBy) + if err != nil { + return false, err + } + var recipients []map[string]string + err = json.Unmarshal([]byte(tx.Data), &recipients) + if err != nil || len(recipients) == 0 { + return false, fmt.Errorf("store.ReadTransaction(%s) => %s", spentBy, tx.Data) + } + return deposit.Index >= uint64(len(recipients)), nil +} + +func (node *Node) verifyMixinKernelTransaction(ctx context.Context, req *common.Request, deposit *Deposit, mtx *common.VersionedTransaction, safe *store.Safe) (*mixin.Input, error) { + input, receiver := mixin.ParseTransactionDepositOutput(safe.Holder, safe.Signer, safe.Observer, mtx, int(deposit.Index)) + if input == nil { + return nil, fmt.Errorf("malicious mixin kernel deposit or node not in sync? %s %d", deposit.Hash, deposit.Index) + } + if input.Asset != crypto.NewHash([]byte(deposit.Asset)) { + return nil, fmt.Errorf("malicious mixin kernel deposit asset %s %d", deposit.Hash, deposit.Index) + } + + if !input.Amount.Equal(decimal.NewFromBigInt(deposit.Amount, -mixin.ValuePrecision)) { + return nil, fmt.Errorf("malicious mixin kernel deposit amount %s %d", deposit.Hash, deposit.Index) + } + if safe.Address != receiver { + return nil, fmt.Errorf("malicious mixin kernel deposit address %s %d", deposit.Hash, deposit.Index) + } + + return input, nil +} diff --git a/keeper/mixin_test.go b/keeper/mixin_test.go new file mode 100644 index 00000000..4637422a --- /dev/null +++ b/keeper/mixin_test.go @@ -0,0 +1,517 @@ +package keeper + +import ( + "context" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "testing" + "time" + + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/mixin/logger" + "github.com/MixinNetwork/safe/apps/bitcoin" + "github.com/MixinNetwork/safe/apps/mixin" + "github.com/MixinNetwork/safe/common" + "github.com/MixinNetwork/safe/signer" + "github.com/MixinNetwork/trusted-group/mtg" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/gofrs/uuid/v5" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/require" +) + +const ( + testMixinKernelAddress = "XINZrJcfd6QoKrR7Q31YY7gk2zvbU1qkAAZ4xBan4KQYeDq7sZ21g4WFsa2bXKoXSWy4sRvr8grSBRmXPxjDBHbF4jaik5om" + testMixinKernelHolderPrivate = "6761ad91d9784b92b14f6be83fdd364a0a722367bbbf8525a95d4153bf8e7008" + testMixinKernelObserverPrivate = "218a231184dc3be90183118b83f854df3057a53f7a9edb886a12d434ed8fcb06" + testMixinKernelDummyHolderPrivate = "1000c89522d07e0acf4bf65c1d07f662e7d6412c49d4881818817b5726d1a802" + testMixinKernelBondAssetId = "afd17288-1765-3d37-ba91-43bb73448ae0" + testMixinKernelTransactionReceiver = "XINZrJcfd6QoKrR7Q31YY7gk2zvbU1qkAAZ4xBan4KQYeDq7sZ21g4WFsa2bXKoXSWy4sRvr8grSBRmXPxjDBHbF4jaik5om" +) + +func TestMixinKeeper(t *testing.T) { + require := require.New(t) + ctx, node, mpc, signers := testMixinKernelPrepare(require) + + observer := testMixinKernelPublicKey(testMixinKernelObserverPrivate) + bondId := testDeployBondContract(ctx, require, node, testMixinKernelAddress, SafeMixinKernelAssetId) + require.Equal(testMixinKernelBondAssetId, bondId) + node.ProcessOutput(ctx, &mtg.Output{AssetID: bondId, Amount: decimal.NewFromInt(1000000), CreatedAt: time.Now()}) + input := &mixin.Input{ + TransactionHash: "74e131b1224af9cb3d644eaf10ed6ee8e9af1dc73981bfc61f3e6cb8d4d4c7e2", + Index: 0, + Amount: decimal.RequireFromString("0.000123"), + } + testMixinKernelObserverHolderDeposit(ctx, require, node, mpc, observer, input, 1) + input = &mixin.Input{ + TransactionHash: "74e131b1224af9cb3d644eaf10ed6ee8e9af1dc73981bfc61f3e6cb8d4d4c7e2", + Index: 1, + Amount: decimal.RequireFromString("0.006877"), + } + testMixinKernelObserverHolderDeposit(ctx, require, node, mpc, observer, input, 2) + + holder := testMixinKernelPublicKey(testMixinKernelHolderPrivate) + outputs, err := node.store.ListAllMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(outputs, 2) + pendings, err := node.store.ListPendingMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(pendings, 0) + + transactionHash := testMixinKernelProposeTransaction(ctx, require, node, mpc, bondId, "3e37ea1c-1455-400d-9642-f6bbcd8c744e", "c330608a509b84a6cc8063249145dbdd880885f8c8c1f6593400a37b399c5f62", "77770004a99c2e0e2b1da4d648755ef19bd95139acbbe6564cfb06dec7cd34931ca72cdc000274e131b1224af9cb3d644eaf10ed6ee8e9af1dc73981bfc61f3e6cb8d4d4c7e2000000000000000074e131b1224af9cb3d644eaf10ed6ee8e9af1dc73981bfc61f3e6cb8d4d4c7e20001000000000000000200000002300c000120253714361d28713900f1b6b09d4b38565fa041cbb4b20da8e8e3169ced26b1a11e825b186b695bc72cdbd312ca8b03457d4c592e33dfd66fa46d226dcfcc220003fffe010000000000030a7e54000162ddce376ba321f5766842f5953b6896992d8d515fb2061247972f6332a1c5b82ef07340f5fd41b9fc5f106bb0df32b651c2ccee1ba66ccb9fc165da60da696d0003fffe0100000000000000103e37ea1c1455400d9642f6bbcd8c744e0000") + outputs, err = node.store.ListAllMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(outputs, 0) + pendings, err = node.store.ListPendingMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(pendings, 2) + testMixinKernelRevokeTransaction(ctx, require, node, transactionHash, false) + outputs, err = node.store.ListAllMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(outputs, 2) + pendings, err = node.store.ListPendingMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(pendings, 0) + + transactionHash = testMixinKernelProposeTransaction(ctx, require, node, mpc, bondId, "b0a22078-0a86-459d-93f4-a1aadbf2b9b7", "633a03e24ec62f28ff1e2769fc98a18dc8141c61db5ab7fdec240e546a384cdc", "77770004a99c2e0e2b1da4d648755ef19bd95139acbbe6564cfb06dec7cd34931ca72cdc000274e131b1224af9cb3d644eaf10ed6ee8e9af1dc73981bfc61f3e6cb8d4d4c7e2000000000000000074e131b1224af9cb3d644eaf10ed6ee8e9af1dc73981bfc61f3e6cb8d4d4c7e20001000000000000000200000002300c0001317c78dd1f1ff39ed14c0ba595536da85c04ffcfc4c349dcb18651c254be3f41369e617a18e7655e927e5f0f3805f5fd8d182888556311f626f441b02ac81a010003fffe010000000000030a7e5400011af4766dbf46790d9d87e0427421b0ce6d9720c8d8327f8c3390eadcb48ac8d196e7f0d15190e22a1c07ed9faa9ecfa54c88b4bc77539ab7afc977b6a541ed690003fffe010000000000000010b0a220780a86459d93f4a1aadbf2b9b70000") + outputs, err = node.store.ListAllMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(outputs, 0) + pendings, err = node.store.ListPendingMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(pendings, 2) + testMixinKernelApproveTransaction(ctx, require, node, transactionHash, signers) + outputs, err = node.store.ListAllMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(outputs, 0) + pendings, err = node.store.ListPendingMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(pendings, 0) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveEdwards25519Mixin) +} + +func testMixinKernelPrepare(require *require.Assertions) (context.Context, *Node, string, []*signer.Node) { + logger.SetLevel(logger.VERBOSE) + ctx, signers := signer.TestPrepare(require) + mpc := signer.TestFROSTPrepareKeys(ctx, require, signers, common.CurveEdwards25519Mixin) + chainCode := make([]byte, 32) + + root, err := os.MkdirTemp("", "safe-keeper-test-") + require.Nil(err) + node := testBuildNode(ctx, require, root) + require.NotNil(node) + timestamp, err := node.timestamp(ctx) + require.Nil(err) + require.Equal(time.Unix(0, node.conf.MTG.Genesis.Timestamp), timestamp) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveEdwards25519Mixin) + + id := uuid.Must(uuid.NewV4()).String() + extra := append([]byte{common.RequestRoleSigner}, chainCode...) + extra = append(extra, common.RequestFlagNone) + out := testBuildSignerOutput(node, id, mpc, common.OperationTypeKeygenOutput, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + v, err := node.store.ReadProperty(ctx, id) + require.Nil(err) + require.Equal("", v) + testSpareKeys(ctx, require, node, 0, 1, 0, common.CurveEdwards25519Mixin) + + id = uuid.Must(uuid.NewV4()).String() + observer := testMixinKernelPublicKey(testMixinKernelObserverPrivate) + occ := make([]byte, 32) + extra = append([]byte{common.RequestRoleObserver}, occ...) + extra = append(extra, common.RequestFlagNone) + out = testBuildObserverRequest(node, id, observer, common.ActionObserverAddKey, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + v, err = node.store.ReadProperty(ctx, id) + require.Nil(err) + require.Equal("", v) + testSpareKeys(ctx, require, node, 0, 1, 1, common.CurveEdwards25519Mixin) + + batch := byte(64) + id = uuid.Must(uuid.NewV4()).String() + dummy := testMixinKernelPublicKey(testMixinKernelDummyHolderPrivate) + out = testBuildObserverRequest(node, id, dummy, common.ActionObserverRequestSignerKeys, []byte{batch}, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + for i := byte(0); i < batch; i++ { + pid := common.UniqueId(id, fmt.Sprintf("%8d", i)) + pid = common.UniqueId(pid, fmt.Sprintf("MTG:%v:%d", node.signer.Genesis.Members, node.signer.Genesis.Threshold)) + v, _ := node.store.ReadProperty(ctx, pid) + var om map[string]any + json.Unmarshal([]byte(v), &om) + b, _ := hex.DecodeString(om["memo"].(string)) + b = common.AESDecrypt(node.signerAESKey[:], b) + o, err := common.DecodeOperation(b) + require.Nil(err) + require.Equal(pid, o.Id) + } + testSpareKeys(ctx, require, node, 0, 1, 1, common.CurveEdwards25519Mixin) + + for i := 0; i < 10; i++ { + testMixinKernelUpdateAccountPrice(ctx, require, node) + } + rid, publicKey := testMixinKernelProposeAccount(ctx, require, node, mpc, observer) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveEdwards25519Mixin) + testMixinKernelApproveAccount(ctx, require, node, mpc, observer, rid, publicKey) + testSpareKeys(ctx, require, node, 0, 0, 0, common.CurveEdwards25519Mixin) + for i := 0; i < 10; i++ { + testMixinKernelUpdateNetworkStatus(ctx, require, node, 641117557, "2192715566293aba968675bd63a211d5489e283c2facfb19456bb51d75b80df6") + } + + return ctx, node, mpc, signers +} + +func testMixinKernelUpdateAccountPrice(ctx context.Context, require *require.Assertions, node *Node) { + id := uuid.Must(uuid.NewV4()).String() + + extra := []byte{SafeChainMixinKernel} + extra = append(extra, uuid.Must(uuid.FromString(testAccountPriceAssetId)).Bytes()...) + extra = binary.BigEndian.AppendUint64(extra, testAccountPriceAmount*100000000) + extra = binary.BigEndian.AppendUint64(extra, 10000) + dummy := testMixinKernelPublicKey(testMixinKernelDummyHolderPrivate) + out := testBuildObserverRequest(node, id, dummy, common.ActionObserverSetOperationParams, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + + plan, err := node.store.ReadLatestOperationParams(ctx, SafeChainMixinKernel, time.Now()) + require.Nil(err) + require.Equal(testAccountPriceAssetId, plan.OperationPriceAsset) + require.Equal(fmt.Sprint(testAccountPriceAmount), plan.OperationPriceAmount.String()) + require.Equal("0.0001", plan.TransactionMinimum.String()) +} + +func testMixinKernelUpdateNetworkStatus(ctx context.Context, require *require.Assertions, node *Node, blockHeight int, blockHash string) { + id := uuid.Must(uuid.NewV4()).String() + fee, height := 0, uint64(blockHeight) + hash, _ := crypto.HashFromString(blockHash) + + extra := []byte{SafeChainMixinKernel} + extra = binary.BigEndian.AppendUint64(extra, uint64(fee)) + extra = binary.BigEndian.AppendUint64(extra, height) + extra = append(extra, hash[:]...) + dummy := testMixinKernelPublicKey(testMixinKernelDummyHolderPrivate) + out := testBuildObserverRequest(node, id, dummy, common.ActionObserverUpdateNetworkStatus, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + + info, err := node.store.ReadLatestNetworkInfo(ctx, SafeChainMixinKernel, time.Now()) + require.Nil(err) + require.NotNil(info) + require.Equal(byte(SafeChainMixinKernel), info.Chain) + require.Equal(uint64(fee), info.Fee) + require.Equal(height, info.Height) + require.Equal(hash.String(), info.Hash) +} + +func testMixinKernelRevokeTransaction(ctx context.Context, require *require.Assertions, node *Node, transactionHash string, signByObserver bool) { + id := uuid.Must(uuid.NewV4()).String() + + tx, _ := node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStateInitial, tx.State) + + var sig crypto.Signature + ms := fmt.Sprintf("REVOKE:%s:%s", tx.RequestId, tx.TransactionHash) + msg := mixin.HashMessageForSignature(ms) + if signByObserver { + key, _ := crypto.KeyFromString(testMixinKernelObserverPrivate) + sig = key.Sign(msg) + } else { + key, _ := crypto.KeyFromString(testMixinKernelHolderPrivate) + sig = key.Sign(msg) + } + extra := uuid.Must(uuid.FromString(tx.RequestId)).Bytes() + extra = append(extra, sig[:]...) + + holder := testMixinKernelPublicKey(testMixinKernelHolderPrivate) + out := testBuildObserverRequest(node, id, holder, common.ActionMixinKernelSafeRevokeTransaction, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + requests, err := node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) + require.Nil(err) + require.Len(requests, 0) + tx, _ = node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStateFailed, tx.State) +} + +func testMixinKernelApproveTransaction(ctx context.Context, require *require.Assertions, node *Node, transactionHash string, signers []*signer.Node) string { + id := uuid.Must(uuid.NewV4()).String() + + tx, _ := node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStateInitial, tx.State) + safe, _ := node.store.ReadSafe(ctx, tx.Holder) + + var data struct { + StorageTransaction crypto.Hash `json:"storage"` + } + json.Unmarshal([]byte(tx.Data), &data) + v, _ := node.store.ReadProperty(ctx, data.StorageTransaction.String()) + for _, sn := range signers { + signer.TestWriteProperty(ctx, sn, data.StorageTransaction.String(), v) + } + + key, _ := crypto.KeyFromString(testMixinKernelHolderPrivate) + ms := fmt.Sprintf("APPROVE:%s:%s", tx.RequestId, tx.TransactionHash) + sig := key.Sign(mixin.HashMessageForSignature(ms)) + extra := uuid.Must(uuid.FromString(tx.RequestId)).Bytes() + extra = append(extra, sig[:]...) + + holder := testMixinKernelPublicKey(testMixinKernelHolderPrivate) + out := testBuildObserverRequest(node, id, holder, common.ActionMixinKernelSafeApproveTransaction, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + requests, err := node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) + require.Nil(err) + require.Len(requests, 2) + tx, _ = node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStatePending, tx.State) + + msg, _ := hex.DecodeString(requests[0].Message) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignInput, msg, common.CurveEdwards25519Mixin) + for _, sn := range signers { + signer.TestWriteProperty(ctx, sn, out.TransactionHash.String(), data.StorageTransaction.String()) + } + op := signer.TestProcessOutput(ctx, require, signers, out, requests[0].RequestId) + out = testBuildSignerOutput(node, requests[0].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStatePending) + require.Len(requests, 1) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) + require.Len(requests, 1) + tx, _ = node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStatePending, tx.State) + + msg, _ = hex.DecodeString(requests[1].Message) + out = testBuildSignerOutput(node, requests[1].RequestId, safe.Signer, common.OperationTypeSignInput, msg, common.CurveEdwards25519Mixin) + for _, sn := range signers { + signer.TestWriteProperty(ctx, sn, out.TransactionHash.String(), data.StorageTransaction.String()) + } + op = signer.TestProcessOutput(ctx, require, signers, out, requests[1].RequestId) + out = testBuildSignerOutput(node, requests[1].RequestId, safe.Signer, common.OperationTypeSignOutput, op.Extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateInitial) + require.Len(requests, 0) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStatePending) + require.Len(requests, 0) + requests, _ = node.store.ListAllSignaturesForTransaction(ctx, transactionHash, common.RequestStateDone) + require.Len(requests, 2) + tx, _ = node.store.ReadTransaction(ctx, transactionHash) + require.Equal(common.RequestStateDone, tx.State) + + signed := make(map[int][]byte) + for _, r := range requests { + b, _ := hex.DecodeString(r.Signature.String) + signed[r.InputIndex] = b + } + mb := common.DecodeHexOrPanic(tx.RawTransaction) + exk := crypto.Blake3Hash([]byte(common.Base91Encode(mb))) + rid := common.UniqueId(transactionHash, hex.EncodeToString(exk[:])) + b := testReadObserverResponse(ctx, require, node, rid, common.ActionMixinKernelSafeApproveTransaction) + require.Equal(mb, b) + + tx, _ = node.store.ReadTransaction(ctx, tx.TransactionHash) + ver, _ := mixin.ParsePartiallySignedTransaction(common.DecodeHexOrPanic(tx.RawTransaction)) + require.Len(ver.SignaturesMap, 2) + require.Equal(signed[0], ver.SignaturesMap[0][0][:]) + require.Equal(signed[1], ver.SignaturesMap[1][0][:]) + signedRaw := hex.EncodeToString(ver.Marshal()) + logger.Println(signedRaw) + return signedRaw +} + +func testMixinKernelProposeTransaction(ctx context.Context, require *require.Assertions, node *Node, signer, bondId string, rid, rhash, rraw string) string { + holder := testMixinKernelPublicKey(testMixinKernelHolderPrivate) + observer := testMixinKernelPublicKey(testMixinKernelObserverPrivate) + info, _ := node.store.ReadLatestNetworkInfo(ctx, SafeChainMixinKernel, time.Now()) + extra := []byte{0} + extra = append(extra, uuid.Must(uuid.FromString(info.RequestId)).Bytes()...) + extra = append(extra, []byte(testMixinKernelTransactionReceiver)...) + out := testBuildHolderRequest(node, rid, holder, common.ActionMixinKernelSafeProposeTransaction, bondId, extra, decimal.NewFromFloat(0.000123)) + testStep(ctx, require, node, out) + + var view crypto.Key + safe, _ := node.store.ReadSafe(ctx, holder) + copy(view[:], safe.Extra) + addr := mixin.BuildAddress(holder, signer, observer) + require.Equal(view, addr.PrivateViewKey) + + b := testReadObserverResponse(ctx, require, node, rid, common.ActionMixinKernelSafeProposeTransaction) + require.Equal(rraw, hex.EncodeToString(b)) + psbt, err := mixin.ParsePartiallySignedTransaction(b) + require.Nil(err) + require.Equal(rhash, psbt.PayloadHash().String()) + + require.Len(psbt.Outputs, 2) + main := psbt.Outputs[0] + require.Equal("0.00012300", main.Amount.String()) + require.Len(main.Keys, 1) + pub := crypto.ViewGhostOutputKey(main.Keys[0], &view, &main.Mask, 0) + require.Equal(signer, pub.String()) + change := psbt.Outputs[1] + require.Equal("0.00687700", change.Amount.String()) + require.Len(change.Keys, 1) + pub = crypto.ViewGhostOutputKey(change.Keys[0], &view, &change.Mask, 1) + require.Equal(signer, pub.String()) + + stx, err := node.store.ReadTransaction(ctx, psbt.PayloadHash().String()) + require.Nil(err) + require.Equal(hex.EncodeToString(psbt.Marshal()), stx.RawTransaction) + require.Equal(common.RequestStateInitial, stx.State) + + if rid == "3e37ea1c-1455-400d-9642-f6bbcd8c744e" { + require.Equal("a11e825b186b695bc72cdbd312ca8b03457d4c592e33dfd66fa46d226dcfcc22", main.Mask.String()) + require.Equal("2ef07340f5fd41b9fc5f106bb0df32b651c2ccee1ba66ccb9fc165da60da696d", change.Mask.String()) + require.Equal("{\"recipients\":[{\"amount\":\"0.000123\",\"receiver\":\"XINZrJcfd6QoKrR7Q31YY7gk2zvbU1qkAAZ4xBan4KQYeDq7sZ21g4WFsa2bXKoXSWy4sRvr8grSBRmXPxjDBHbF4jaik5om\"}],\"storage\":\"08b87919fb1b36270bffbc8a21ad7d9d1175ec7eb37d9cc84c3d4e5a96a0cbf0\"}", stx.Data) + } else { + require.Equal("369e617a18e7655e927e5f0f3805f5fd8d182888556311f626f441b02ac81a01", main.Mask.String()) + require.Equal("96e7f0d15190e22a1c07ed9faa9ecfa54c88b4bc77539ab7afc977b6a541ed69", change.Mask.String()) + require.Equal("{\"recipients\":[{\"amount\":\"0.000123\",\"receiver\":\"XINZrJcfd6QoKrR7Q31YY7gk2zvbU1qkAAZ4xBan4KQYeDq7sZ21g4WFsa2bXKoXSWy4sRvr8grSBRmXPxjDBHbF4jaik5om\"}],\"storage\":\"0f692ca9b1152706967873513dc9e518ab51b1acbf1245e5e592dce332b5ff73\"}", stx.Data) + } + return stx.TransactionHash +} + +func testMixinKernelHolderApproveTransaction(rawTransaction string) string { + hb := common.DecodeHexOrPanic(testBitcoinKeyHolderPrivate) + holder, _ := btcec.PrivKeyFromBytes(hb) + + psTx, _ := bitcoin.UnmarshalPartiallySignedTransaction(common.DecodeHexOrPanic(rawTransaction)) + for idx := range psTx.UnsignedTx.TxIn { + hash := psTx.SigHash(idx) + sig := ecdsa.Sign(holder, hash).Serialize() + psTx.Inputs[idx].PartialSigs = []*psbt.PartialSig{{ + PubKey: holder.PubKey().SerializeCompressed(), + Signature: sig, + }} + } + raw := psTx.Marshal() + return hex.EncodeToString(raw) +} + +func (node *Node) testMixinKernelSignerHolderApproveTransaction(ctx context.Context, require *require.Assertions, rawTransaction string, signed map[int][]byte, signer, path string) *wire.MsgTx { + hb := common.DecodeHexOrPanic(testBitcoinKeyHolderPrivate) + holder, _ := btcec.PrivKeyFromBytes(hb) + + b, _ := hex.DecodeString(rawTransaction) + psbt, _ := bitcoin.UnmarshalPartiallySignedTransaction(b) + msgTx := psbt.UnsignedTx + for idx := range msgTx.TxIn { + pop := msgTx.TxIn[idx].PreviousOutPoint + hash := psbt.SigHash(idx) + utxo, _, _ := node.store.ReadBitcoinUTXO(ctx, pop.Hash.String(), int(pop.Index)) + msig := signed[idx] + if msig == nil { + continue + } + + msig = append(msig, byte(bitcoin.SigHashType)) + der, _ := ecdsa.ParseDERSignature(msig[:len(msig)-1]) + pub, _ := node.deriveBIP32WithPath(ctx, signer, common.DecodeHexOrPanic(path)) + signer, _ := btcutil.NewAddressPubKey(common.DecodeHexOrPanic(pub), &chaincfg.MainNetParams) + require.True(der.Verify(hash, signer.PubKey())) + + msgTx.TxIn[idx].Witness = append(msgTx.TxIn[idx].Witness, []byte{}) + msgTx.TxIn[idx].Witness = append(msgTx.TxIn[idx].Witness, msig) + + signature := ecdsa.Sign(holder, hash) + sig := append(signature.Serialize(), byte(bitcoin.SigHashType)) + msgTx.TxIn[idx].Witness = append(msgTx.TxIn[idx].Witness, sig) + msgTx.TxIn[idx].Witness = append(msgTx.TxIn[idx].Witness, utxo.Script) + } + + return msgTx +} + +func testMixinKernelObserverHolderDeposit(ctx context.Context, require *require.Assertions, node *Node, signer, observer string, input *mixin.Input, t int) { + id := uuid.Must(uuid.NewV4()).String() + hash, _ := crypto.HashFromString(input.TransactionHash) + iam := input.Amount.Mul(decimal.New(1, mixin.ValuePrecision)) + if !iam.IsInteger() { + panic(input.Amount.String()) + } + extra := []byte{SafeChainMixinKernel} + extra = append(extra, uuid.Must(uuid.FromString(SafeMixinKernelAssetId)).Bytes()...) + extra = append(extra, hash[:]...) + extra = binary.BigEndian.AppendUint64(extra, uint64(input.Index)) + extra = append(extra, iam.BigInt().Bytes()...) + + holder := testMixinKernelPublicKey(testMixinKernelHolderPrivate) + + out := testBuildObserverRequest(node, id, holder, common.ActionObserverHolderDeposit, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + + mainInputs, err := node.store.ListAllMixinKernelUTXOsForHolderAndAsset(ctx, holder, SafeMixinKernelAssetId) + require.Nil(err) + require.Len(mainInputs, t) + utxo := mainInputs[t-1] + require.Equal(uint32(input.Index), utxo.Index) + require.Equal(input.Amount, utxo.Amount) + require.Equal(hash.String(), utxo.TransactionHash) +} + +func testMixinKernelProposeAccount(ctx context.Context, require *require.Assertions, node *Node, signer, observer string) (string, string) { + id := uuid.Must(uuid.NewV4()).String() + holder := testMixinKernelPublicKey(testMixinKernelHolderPrivate) + extra := testRecipient() + price := decimal.NewFromFloat(testAccountPriceAmount) + out := testBuildHolderRequest(node, id, holder, common.ActionMixinKernelSafeProposeAccount, testAccountPriceAssetId, extra, price) + testStep(ctx, require, node, out) + b := testReadObserverResponse(ctx, require, node, id, common.ActionMixinKernelSafeProposeAccount) + wsa, err := mixin.ParseAddress(string(b)) + require.Nil(err) + require.Equal(testMixinKernelAddress, wsa.String()) + + safe, err := node.store.ReadSafeProposal(ctx, id) + require.Nil(err) + require.Equal(id, safe.RequestId) + require.Equal(holder, safe.Holder) + require.Equal(signer, safe.Signer) + require.Equal(observer, safe.Observer) + public := mixin.BuildAddress(holder, signer, observer) + require.Equal(testMixinKernelAddress, public.String()) + require.Equal(public.String(), safe.Address) + require.Equal(byte(1), safe.Threshold) + require.Len(safe.Receivers, 1) + require.Equal(testSafeBondReceiverId, safe.Receivers[0]) + + return id, wsa.String() +} + +func testMixinKernelApproveAccount(ctx context.Context, require *require.Assertions, node *Node, signer, observer string, rid, publicKey string) { + id := uuid.Must(uuid.NewV4()).String() + holder := testMixinKernelPublicKey(testMixinKernelHolderPrivate) + ms := fmt.Sprintf("APPROVE:%s:%s", rid, publicKey) + hash := mixin.HashMessageForSignature(ms) + hp, _ := crypto.KeyFromString(testMixinKernelHolderPrivate) + signature := hp.Sign(hash) + extra := uuid.FromStringOrNil(rid).Bytes() + extra = append(extra, signature[:]...) + out := testBuildObserverRequest(node, id, holder, common.ActionMixinKernelSafeApproveAccount, extra, common.CurveEdwards25519Mixin) + testStep(ctx, require, node, out) + b := testReadObserverResponse(ctx, require, node, id, common.ActionMixinKernelSafeApproveAccount) + wsa, err := mixin.ParseAddress(string(b)) + require.Nil(err) + require.Equal(testMixinKernelAddress, wsa.String()) + + safe, err := node.store.ReadSafe(ctx, holder) + require.Nil(err) + require.Equal(id, safe.RequestId) + require.Equal(holder, safe.Holder) + require.Equal(signer, safe.Signer) + require.Equal(observer, safe.Observer) + public := mixin.BuildAddress(holder, signer, observer) + require.Equal(testMixinKernelAddress, public.String()) + require.Equal(public.String(), safe.Address) + require.Equal(byte(1), safe.Threshold) + require.Len(safe.Receivers, 1) + require.Equal(testSafeBondReceiverId, safe.Receivers[0]) + var view crypto.Key + copy(view[:], safe.Extra) + require.Equal(view, public.PrivateViewKey) +} + +func testMixinKernelPublicKey(priv string) string { + key, _ := crypto.KeyFromString(priv) + return key.Public().String() +} diff --git a/keeper/network.go b/keeper/network.go index 1e8d8743..45e02098 100644 --- a/keeper/network.go +++ b/keeper/network.go @@ -53,6 +53,9 @@ func (node *Node) writeNetworkInfo(ctx context.Context, req *common.Request) err switch info.Chain { case SafeChainBitcoin, SafeChainLitecoin: + if info.Chain != BitcoinCurveChain(req.Curve) { + panic(req.Id) + } info.Hash = hex.EncodeToString(extra[17:]) valid, err := node.verifyBitcoinNetworkInfo(ctx, info) if err != nil { @@ -60,6 +63,20 @@ func (node *Node) writeNetworkInfo(ctx context.Context, req *common.Request) err } else if !valid { return node.store.FailRequest(ctx, req.Id) } + case SafeChainMixinKernel: + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Id) + } + info.Hash = hex.EncodeToString(extra[17:]) + hash, _ := crypto.HashFromString(info.Hash) + snap, err := common.ReadKernelSnapshot(node.conf.MixinRPC, hash) + if err != nil { + return fmt.Errorf("common.ReadKernelSnapshot(%v) => %v", info, err) + } else if snap.TopologicalOrder > info.Height && snap.TopologicalOrder-info.Height > 10000 { + panic(info.Hash) + } else if snap.TopologicalOrder < info.Height && info.Height-snap.TopologicalOrder > 10000 { + panic(info.Hash) + } case SafeChainEthereum: panic(0) default: @@ -81,15 +98,18 @@ func (node *Node) writeOperationParams(ctx context.Context, req *common.Request) chain := extra[0] switch chain { - case SafeChainBitcoin: - case SafeChainLitecoin: + case SafeChainBitcoin, SafeChainLitecoin: + if chain != BitcoinCurveChain(req.Curve) { + panic(req.Id) + } + case SafeChainMixinKernel: + if req.Curve != common.CurveEdwards25519Mixin { + panic(req.Id) + } case SafeChainEthereum: default: return node.store.FailRequest(ctx, req.Id) } - if chain != BitcoinCurveChain(req.Curve) { - panic(req.Id) - } assetId := uuid.Must(uuid.FromBytes(extra[1:17])) abu := new(big.Int).SetUint64(binary.BigEndian.Uint64(extra[17:25])) diff --git a/keeper/node.go b/keeper/node.go index 33fdfedc..5df05cd2 100644 --- a/keeper/node.go +++ b/keeper/node.go @@ -11,7 +11,6 @@ import ( "github.com/MixinNetwork/safe/common/abi" "github.com/MixinNetwork/safe/keeper/store" "github.com/MixinNetwork/trusted-group/mtg" - "github.com/fox-one/mixin-sdk-go" ) type Node struct { @@ -76,7 +75,7 @@ func (node *Node) buildTransactionWithReferences(ctx context.Context, assetId st }) return node.store.WriteProperty(ctx, traceId, string(v)) } - traceId = mixin.UniqueConversationID(node.group.GenesisId(), traceId) + traceId = common.UniqueId(node.group.GenesisId(), traceId) if tx.HasValue() { return node.group.BuildTransactionWithReferences(ctx, assetId, receivers, threshold, amount, string(memo), traceId, "", []crypto.Hash{tx}) } diff --git a/keeper/signer.go b/keeper/signer.go index 0ebfe220..aa245622 100644 --- a/keeper/signer.go +++ b/keeper/signer.go @@ -9,7 +9,6 @@ import ( "github.com/MixinNetwork/mixin/logger" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/keeper/store" - "github.com/fox-one/mixin-sdk-go" ) const ( @@ -26,6 +25,7 @@ func (node *Node) sendSignerKeygenRequest(ctx context.Context, req *common.Reque crv := common.NormalizeCurve(req.Curve) switch crv { case common.CurveSecp256k1ECDSABitcoin: + case common.CurveEdwards25519Mixin: default: return node.store.FailRequest(ctx, req.Id) } @@ -39,8 +39,8 @@ func (node *Node) sendSignerKeygenRequest(ctx context.Context, req *common.Reque Type: common.OperationTypeKeygenInput, Curve: crv, } - op.Id = mixin.UniqueConversationID(req.Id, fmt.Sprintf("%8d", i)) - op.Id = mixin.UniqueConversationID(op.Id, fmt.Sprintf("MTG:%v:%d", node.signer.Genesis.Members, node.signer.Genesis.Threshold)) + op.Id = common.UniqueId(req.Id, fmt.Sprintf("%8d", i)) + op.Id = common.UniqueId(op.Id, fmt.Sprintf("MTG:%v:%d", node.signer.Genesis.Members, node.signer.Genesis.Threshold)) err := node.buildSignerTransaction(ctx, op) if err != nil { return err @@ -54,6 +54,7 @@ func (node *Node) sendSignerSignRequest(ctx context.Context, req *store.Signatur crv := common.NormalizeCurve(req.Curve) switch crv { case common.CurveSecp256k1ECDSABitcoin: + case common.CurveEdwards25519Mixin: default: panic(req.Curve) } diff --git a/keeper/store/mixin.go b/keeper/store/mixin.go new file mode 100644 index 00000000..d1e9964f --- /dev/null +++ b/keeper/store/mixin.go @@ -0,0 +1,139 @@ +package store + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/MixinNetwork/mixin/crypto" + "github.com/MixinNetwork/safe/apps/mixin" + "github.com/MixinNetwork/safe/common" + "github.com/shopspring/decimal" +) + +func (s *SQLite3Store) WriteMixinKernelOutputFromRequest(ctx context.Context, receiver, assetId string, utxo *mixin.Input, req *common.Request) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + cols := []string{"transaction_hash", "output_index", "address", "asset_id", "amount", "mask", "chain", "state", "spent_by", "request_id", "created_at", "updated_at"} + vals := []any{utxo.TransactionHash, utxo.Index, receiver, assetId, utxo.Amount.String(), utxo.Mask.String(), mixin.ChainMixinKernel, common.RequestStateInitial, nil, req.Id, req.CreatedAt, req.CreatedAt} + err = s.execOne(ctx, tx, buildInsertionSQL("mixin_outputs", cols), vals...) + if err != nil { + return fmt.Errorf("INSERT mixin_outputs %v", err) + } + err = s.execOne(ctx, tx, "UPDATE requests SET state=?, updated_at=? WHERE request_id=?", common.RequestStateDone, time.Now().UTC(), req.Id) + if err != nil { + return fmt.Errorf("UPDATE requests %v", err) + } + return tx.Commit() +} + +func (s *SQLite3Store) ReadMixinKernelUTXO(ctx context.Context, transactionHash string, index int) (*mixin.Input, string, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, "", err + } + defer tx.Rollback() + + return s.readMixinKernelUTXO(ctx, tx, transactionHash, index) +} + +func (s *SQLite3Store) ListAllMixinKernelUTXOsForHolderAndAsset(ctx context.Context, holder, assetId string) ([]*mixin.Input, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + safe, err := s.readSafe(ctx, tx, holder) + if err != nil { + return nil, err + } + + mainInputs, err := s.listAllMixinKernelUTXOsForAddressAndAsset(ctx, safe.Address, assetId, common.RequestStateInitial) + if err != nil { + return nil, err + } + + return mainInputs, nil +} + +func (s *SQLite3Store) ListPendingMixinKernelUTXOsForHolderAndAsset(ctx context.Context, holder, assetId string) ([]*mixin.Input, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + safe, err := s.readSafe(ctx, tx, holder) + if err != nil { + return nil, err + } + + mainInputs, err := s.listAllMixinKernelUTXOsForAddressAndAsset(ctx, safe.Address, assetId, common.RequestStatePending) + if err != nil { + return nil, err + } + + return mainInputs, nil +} + +func (s *SQLite3Store) listAllMixinKernelUTXOsForAddressAndAsset(ctx context.Context, receiver, assetId string, state int) ([]*mixin.Input, error) { + query := "SELECT transaction_hash,output_index,amount,asset_id,mask FROM mixin_outputs WHERE address=? AND asset_id=? AND state=? LIMIT 256" + rows, err := s.db.QueryContext(ctx, query, receiver, assetId, state) + if err != nil { + return nil, err + } + defer rows.Close() + + var inputs []*mixin.Input + for rows.Next() { + var input mixin.Input + var amount, asset, mask string + err := rows.Scan(&input.TransactionHash, &input.Index, &amount, &asset, &mask) + if err != nil { + return nil, err + } + input.Amount = decimal.RequireFromString(amount) + input.Asset = crypto.NewHash([]byte(asset)) + input.Mask, err = crypto.KeyFromString(mask) + if err != nil { + panic(err) + } + inputs = append(inputs, &input) + } + return inputs, nil +} + +func (s *SQLite3Store) readMixinKernelUTXO(ctx context.Context, tx *sql.Tx, transactionHash string, index int) (*mixin.Input, string, error) { + input := &mixin.Input{ + TransactionHash: transactionHash, + Index: uint32(index), + } + + query := "SELECT amount,asset_id,mask,spent_by FROM mixin_outputs WHERE transaction_hash=? AND output_index=?" + row := tx.QueryRowContext(ctx, query, transactionHash, index) + + var spent sql.NullString + var amount, asset, mask string + err := row.Scan(&amount, &asset, &mask, &spent) + if err == sql.ErrNoRows { + return nil, "", nil + } else if err != nil { + return nil, "", err + } + input.Amount = decimal.RequireFromString(amount) + input.Asset = crypto.NewHash([]byte(asset)) + input.Mask, err = crypto.KeyFromString(mask) + if err != nil { + panic(err) + } + return input, spent.String, nil +} diff --git a/keeper/store/schema.sql b/keeper/store/schema.sql index 1ac587b8..0d4e7a88 100644 --- a/keeper/store/schema.sql +++ b/keeper/store/schema.sql @@ -160,12 +160,33 @@ CREATE UNIQUE INDEX IF NOT EXISTS bitcoin_outputs_by_request_id ON bitcoin_outpu +CREATE TABLE IF NOT EXISTS mixin_outputs ( + transaction_hash VARCHAR NOT NULL, + output_index INTEGER NOT NULL, + address VARCHAR NOT NULL, + asset_id VARCHAR NOT NULL, + amount VARCHAR NOT NULL, + mask VARCHAR NOT NULL, + chain INTEGER NOT NULL, + state INTEGER NOT NULL, + spent_by VARCHAR, + request_id VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + PRIMARY KEY ('transaction_hash', 'output_index') +); + +CREATE UNIQUE INDEX IF NOT EXISTS mixin_outputs_by_request_id ON mixin_outputs(request_id); + + + CREATE TABLE IF NOT EXISTS transactions ( transaction_hash VARCHAR NOT NULL, raw_transaction VARCHAR NOT NULL, holder VARCHAR NOT NULL, chain INTEGER NOT NULL, + asset_id VARCHAR NOT NULL, state INTEGER NOT NULL, data VARCHAR NOT NULL, request_id VARCHAR NOT NULL, diff --git a/keeper/store/signature.go b/keeper/store/signature.go index acc7b08d..8f4ff763 100644 --- a/keeper/store/signature.go +++ b/keeper/store/signature.go @@ -121,7 +121,7 @@ func (s *SQLite3Store) FinishSignatureRequest(ctx context.Context, req *common.R return tx.Commit() } -func (s *SQLite3Store) FinishTransactionSignaturesWithRequest(ctx context.Context, transactionHash, psbt string, req *common.Request, num int64) error { +func (s *SQLite3Store) FinishTransactionSignaturesWithRequest(ctx context.Context, transactionHash, psbt string, req *common.Request, num int64, chain byte) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -143,8 +143,9 @@ func (s *SQLite3Store) FinishTransactionSignaturesWithRequest(ctx context.Contex return fmt.Errorf("UPDATE transactions %v", err) } - err = s.execMultiple(ctx, tx, num, "UPDATE bitcoin_outputs SET state=?, updated_at=? WHERE spent_by=?", - common.RequestStateDone, req.CreatedAt, transactionHash) + table := transactionInputTable(chain) + update := fmt.Sprintf("UPDATE %s SET state=?, updated_at=? WHERE spent_by=?", table) + err = s.execMultiple(ctx, tx, num, update, common.RequestStateDone, req.CreatedAt, transactionHash) if err != nil { return fmt.Errorf("UPDATE bitcoin_outputs %v", err) } diff --git a/keeper/store/transaction.go b/keeper/store/transaction.go index 8d6e2347..4308b454 100644 --- a/keeper/store/transaction.go +++ b/keeper/store/transaction.go @@ -8,6 +8,7 @@ import ( "time" "github.com/MixinNetwork/safe/apps/bitcoin" + "github.com/MixinNetwork/safe/apps/mixin" "github.com/MixinNetwork/safe/common" ) @@ -16,6 +17,7 @@ type Transaction struct { RawTransaction string Holder string Chain byte + AssetId string State int Data string RequestId string @@ -23,7 +25,50 @@ type Transaction struct { UpdatedAt time.Time } -var transactionCols = []string{"transaction_hash", "raw_transaction", "holder", "chain", "state", "data", "request_id", "created_at", "updated_at"} +type TransactionInput struct { + Hash string + Index uint32 +} + +var transactionCols = []string{"transaction_hash", "raw_transaction", "holder", "chain", "asset_id", "state", "data", "request_id", "created_at", "updated_at"} + +func TransactionInputsFromBitcoin(mainInputs []*bitcoin.Input) []*TransactionInput { + inputs := make([]*TransactionInput, len(mainInputs)) + for i, in := range mainInputs { + inputs[i] = &TransactionInput{ + Hash: in.TransactionHash, + Index: in.Index, + } + } + return inputs +} + +func TransactionInputsFromRawTransaction(trx *Transaction) []*TransactionInput { + b := common.DecodeHexOrPanic(trx.RawTransaction) + var inputs []*TransactionInput + switch trx.Chain { + case bitcoin.ChainBitcoin, bitcoin.ChainLitecoin: + psbt, _ := bitcoin.UnmarshalPartiallySignedTransaction(b) + for _, in := range psbt.UnsignedTx.TxIn { + pop := in.PreviousOutPoint + inputs = append(inputs, &TransactionInput{ + Hash: pop.Hash.String(), + Index: pop.Index, + }) + } + case mixin.ChainMixinKernel: + ver, _ := mixin.ParsePartiallySignedTransaction(b) + for _, in := range ver.Inputs { + inputs = append(inputs, &TransactionInput{ + Hash: in.Hash.String(), + Index: uint32(in.Index), + }) + } + default: + panic(trx.Chain) + } + return inputs +} func (s *SQLite3Store) ReadTransactionByRequestId(ctx context.Context, requestId string) (*Transaction, error) { tx, err := s.db.BeginTx(ctx, nil) @@ -67,7 +112,7 @@ func (s *SQLite3Store) CountUnfinishedTransactionsByHolder(ctx context.Context, return count, err } -func (s *SQLite3Store) CloseAccountByTransactionWithRequest(ctx context.Context, trx *Transaction, utxos []*bitcoin.Input, utxoState int) error { +func (s *SQLite3Store) CloseAccountByTransactionWithRequest(ctx context.Context, trx *Transaction, utxos []*TransactionInput, utxoState int) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -90,7 +135,7 @@ func (s *SQLite3Store) CloseAccountByTransactionWithRequest(ctx context.Context, return tx.Commit() } -func (s *SQLite3Store) WriteTransactionWithRequest(ctx context.Context, trx *Transaction, utxos []*bitcoin.Input) error { +func (s *SQLite3Store) WriteTransactionWithRequest(ctx context.Context, trx *Transaction, utxos []*TransactionInput) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -107,8 +152,8 @@ func (s *SQLite3Store) WriteTransactionWithRequest(ctx context.Context, trx *Tra return tx.Commit() } -func (s *SQLite3Store) writeTransactionWithRequest(ctx context.Context, tx *sql.Tx, trx *Transaction, utxos []*bitcoin.Input, utxoState int) error { - vals := []any{trx.TransactionHash, trx.RawTransaction, trx.Holder, trx.Chain, trx.State, trx.Data, trx.RequestId, trx.CreatedAt, trx.UpdatedAt} +func (s *SQLite3Store) writeTransactionWithRequest(ctx context.Context, tx *sql.Tx, trx *Transaction, utxos []*TransactionInput, utxoState int) error { + vals := []any{trx.TransactionHash, trx.RawTransaction, trx.Holder, trx.Chain, trx.AssetId, trx.State, trx.Data, trx.RequestId, trx.CreatedAt, trx.UpdatedAt} err := s.execOne(ctx, tx, buildInsertionSQL("transactions", transactionCols), vals...) if err != nil { return fmt.Errorf("INSERT transactions %v", err) @@ -118,11 +163,12 @@ func (s *SQLite3Store) writeTransactionWithRequest(ctx context.Context, tx *sql. if err != nil { return fmt.Errorf("UPDATE requests %v", err) } + table := transactionInputTable(trx.Chain) + query := fmt.Sprintf("UPDATE %s SET state=?, spent_by=?, updated_at=? WHERE transaction_hash=? AND output_index=?", table) for _, utxo := range utxos { - err = s.execOne(ctx, tx, "UPDATE bitcoin_outputs SET state=?, spent_by=?, updated_at=? WHERE transaction_hash=? AND output_index=?", - utxoState, trx.TransactionHash, trx.UpdatedAt, utxo.TransactionHash, utxo.Index) + err = s.execOne(ctx, tx, query, utxoState, trx.TransactionHash, trx.UpdatedAt, utxo.Hash, utxo.Index) if err != nil { - return fmt.Errorf("UPDATE bitcoin_outputs %v", err) + return fmt.Errorf("UPDATE %s %v", table, err) } } return nil @@ -138,25 +184,23 @@ func (s *SQLite3Store) RevokeTransactionWithRequest(ctx context.Context, trx *Tr } defer tx.Rollback() - psbt, _ := bitcoin.UnmarshalPartiallySignedTransaction(common.DecodeHexOrPanic(trx.RawTransaction)) - for _, in := range psbt.UnsignedTx.TxIn { - pop := in.PreviousOutPoint - err = s.execOne(ctx, tx, "UPDATE bitcoin_outputs SET state=?, spent_by=?, updated_at=? WHERE transaction_hash=? AND output_index=? AND spent_by=?", - common.RequestStateInitial, nil, req.CreatedAt, pop.Hash.String(), pop.Index, trx.TransactionHash) + table := transactionInputTable(trx.Chain) + inputs := TransactionInputsFromRawTransaction(trx) + update := fmt.Sprintf("UPDATE %s SET state=?, spent_by=?, updated_at=? WHERE transaction_hash=? AND output_index=? AND spent_by=?", table) + query := fmt.Sprintf("SELECT address FROM %s WHERE transaction_hash=? AND output_index=?", table) + for _, in := range inputs { + err = s.execOne(ctx, tx, update, common.RequestStateInitial, nil, req.CreatedAt, in.Hash, in.Index, trx.TransactionHash) if err != nil { - return fmt.Errorf("UPDATE bitcoin_outputs %v", err) + return fmt.Errorf("UPDATE %s %v", table, err) } - row := tx.QueryRowContext(ctx, "SELECT address,satoshi FROM bitcoin_outputs WHERE transaction_hash=? AND output_index=?", - pop.Hash.String(), pop.Index) - var u bitcoin.Input - err = row.Scan(&u.TransactionHash, &u.Satoshi) + var receiver string + row := tx.QueryRowContext(ctx, query, in.Hash, in.Index) + err = row.Scan(&receiver) if err != nil { return err } - switch u.TransactionHash { - case safe.Address: - default: + if receiver != safe.Address { panic(trx.TransactionHash) } } @@ -181,9 +225,20 @@ func (s *SQLite3Store) readTransaction(ctx context.Context, tx *sql.Tx, transact row := tx.QueryRowContext(ctx, query, transactionHash) var trx Transaction - err := row.Scan(&trx.TransactionHash, &trx.RawTransaction, &trx.Holder, &trx.Chain, &trx.State, &trx.Data, &trx.RequestId, &trx.CreatedAt, &trx.UpdatedAt) + err := row.Scan(&trx.TransactionHash, &trx.RawTransaction, &trx.Holder, &trx.Chain, &trx.AssetId, &trx.State, &trx.Data, &trx.RequestId, &trx.CreatedAt, &trx.UpdatedAt) if err == sql.ErrNoRows { return nil, nil } return &trx, err } + +func transactionInputTable(chain byte) string { + switch chain { + case bitcoin.ChainBitcoin, bitcoin.ChainLitecoin: + return "bitcoin_outputs" + case mixin.ChainMixinKernel: + return "mixin_outputs" + default: + panic(chain) + } +} diff --git a/observer/bitcoin.go b/observer/bitcoin.go index a9f669c6..c626f405 100644 --- a/observer/bitcoin.go +++ b/observer/bitcoin.go @@ -102,8 +102,8 @@ func (node *Node) bitcoinNetworkInfoLoop(ctx context.Context, chain byte) { dummy := node.bitcoinDummyHolder() action := common.ActionObserverUpdateNetworkStatus - err = node.sendBitcoinKeeperResponse(ctx, dummy, byte(action), chain, id, extra) - logger.Verbosef("node.sendBitcoinKeeperResponse(%d, %s, %x) => %v", chain, id, extra, err) + err = node.sendKeeperResponse(ctx, dummy, byte(action), chain, id, extra) + logger.Verbosef("node.sendKeeperResponse(%d, %s, %x) => %v", chain, id, extra, err) } } @@ -302,9 +302,9 @@ func (node *Node) bitcoinConfirmPendingDeposit(ctx context.Context, deposit *Dep extra := deposit.encodeKeeperExtra() id := mixin.UniqueConversationID(assetId, deposit.Holder) id = mixin.UniqueConversationID(id, fmt.Sprintf("%s:%d", deposit.TransactionHash, deposit.OutputIndex)) - err = node.sendBitcoinKeeperResponse(ctx, deposit.Holder, deposit.Category, deposit.Chain, id, extra) + err = node.sendKeeperResponse(ctx, deposit.Holder, deposit.Category, deposit.Chain, id, extra) if err != nil { - return fmt.Errorf("node.sendBitcoinKeeperResponse(%s) => %v", id, err) + return fmt.Errorf("node.sendKeeperResponse(%s) => %v", id, err) } err = node.store.ConfirmPendingDeposit(ctx, deposit.TransactionHash, deposit.OutputIndex) if err != nil { diff --git a/observer/holder.go b/observer/holder.go index d44a7a73..dad96921 100644 --- a/observer/holder.go +++ b/observer/holder.go @@ -110,7 +110,7 @@ func (node *Node) httpApproveBitcoinAccount(ctx context.Context, addr, sigBase64 rid := uuid.Must(uuid.FromString(sp.RequestId)) extra := append(rid.Bytes(), sig...) action := common.ActionBitcoinSafeApproveAccount - return node.sendBitcoinKeeperResponse(ctx, sp.Holder, byte(action), sp.Chain, id, extra) + return node.sendKeeperResponse(ctx, sp.Holder, byte(action), sp.Chain, id, extra) } func (node *Node) httpCreateBitcoinAccountRecoveryRequest(ctx context.Context, addr, raw, hash string) error { @@ -422,8 +422,8 @@ func (node *Node) httpSignBitcoinAccountRecoveryRequest(ctx context.Context, add extra = append(extra, ref[:]...) action := common.ActionBitcoinSafeCloseAccount references := []crypto.Hash{ref} - err = node.sendBitcoinKeeperResponseWithReferences(ctx, safe.Holder, byte(action), safe.Chain, id, extra, references) - logger.Printf("node.sendBitcoinKeeperResponseWithReferences(%s, %s, %x, %v) => %v", safe.Holder, id, extra, references, err) + err = node.sendKeeperResponseWithReferences(ctx, safe.Holder, byte(action), safe.Chain, id, extra, references) + logger.Printf("node.sendKeeperResponseWithReferences(%s, %s, %x, %v) => %v", safe.Holder, id, extra, references, err) if err != nil { return err } @@ -525,8 +525,8 @@ func (node *Node) httpRevokeBitcoinTransaction(ctx context.Context, txHash strin rid := uuid.Must(uuid.FromString(tx.RequestId)) extra := append(rid.Bytes(), sig...) action := common.ActionBitcoinSafeRevokeTransaction - err = node.sendBitcoinKeeperResponse(ctx, tx.Holder, byte(action), approval.Chain, id, extra) - logger.Printf("node.sendBitcoinKeeperResponse(%s, %d, %s, %x)", tx.Holder, action, id, extra) + err = node.sendKeeperResponse(ctx, tx.Holder, byte(action), approval.Chain, id, extra) + logger.Printf("node.sendKeeperResponse(%s, %d, %s, %x)", tx.Holder, action, id, extra) if err != nil { return err } @@ -604,8 +604,8 @@ func (node *Node) sendToKeeperBitcoinApproveTransaction(ctx context.Context, app extra := append(rid.Bytes(), ref[:]...) references := []crypto.Hash{ref} action := common.ActionBitcoinSafeApproveTransaction - err = node.sendBitcoinKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) - logger.Printf("node.sendBitcoinKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) + err = node.sendKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) + logger.Printf("node.sendKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) if err != nil { return err } @@ -614,8 +614,8 @@ func (node *Node) sendToKeeperBitcoinApproveTransaction(ctx context.Context, app return nil } id = mixin.UniqueConversationID(id, approval.UpdatedAt.String()) - err = node.sendBitcoinKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) - logger.Printf("node.sendBitcoinKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) + err = node.sendKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) + logger.Printf("node.sendKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) if err != nil { return err } diff --git a/observer/keeper.go b/observer/keeper.go index bccda422..8ed4fbcc 100644 --- a/observer/keeper.go +++ b/observer/keeper.go @@ -41,16 +41,18 @@ func (node *Node) checkSafeInternalAddress(ctx context.Context, receiver string) return safe != nil, nil } -func (node *Node) sendBitcoinKeeperResponse(ctx context.Context, holder string, typ, chain uint8, id string, extra []byte) error { - return node.sendBitcoinKeeperResponseWithReferences(ctx, holder, typ, chain, id, extra, nil) +func (node *Node) sendKeeperResponse(ctx context.Context, holder string, typ, chain uint8, id string, extra []byte) error { + return node.sendKeeperResponseWithReferences(ctx, holder, typ, chain, id, extra, nil) } -func (node *Node) sendBitcoinKeeperResponseWithReferences(ctx context.Context, holder string, typ, chain uint8, id string, extra []byte, references []crypto.Hash) error { +func (node *Node) sendKeeperResponseWithReferences(ctx context.Context, holder string, typ, chain uint8, id string, extra []byte, references []crypto.Hash) error { crv := byte(common.CurveSecp256k1ECDSABitcoin) switch chain { case keeper.SafeChainBitcoin: case keeper.SafeChainLitecoin: crv = common.CurveSecp256k1ECDSALitecoin + case keeper.SafeChainMixinKernel: + crv = common.CurveEdwards25519Mixin default: panic(chain) } diff --git a/observer/key.go b/observer/key.go index ecec0aea..324f6c84 100644 --- a/observer/key.go +++ b/observer/key.go @@ -41,7 +41,7 @@ func (node *Node) bitcoinAddObserverKeys(ctx context.Context) error { id := mixin.UniqueConversationID(observer, observer) extra := append([]byte{common.RequestRoleObserver}, chainCode...) extra = append(extra, common.RequestFlagNone) - err = node.sendBitcoinKeeperResponse(ctx, observer, common.ActionObserverAddKey, keeper.SafeChainBitcoin, id, extra) + err = node.sendKeeperResponse(ctx, observer, common.ActionObserverAddKey, keeper.SafeChainBitcoin, id, extra) if err != nil { return err } @@ -65,7 +65,7 @@ func (node *Node) bitcoinRequestSignerKeys(ctx context.Context) error { } dummy := node.bitcoinDummyHolder() id := mixin.UniqueConversationID(requested.String(), requested.String()) - err = node.sendBitcoinKeeperResponse(ctx, dummy, common.ActionObserverRequestSignerKeys, keeper.SafeChainBitcoin, id, []byte{64}) + err = node.sendKeeperResponse(ctx, dummy, common.ActionObserverRequestSignerKeys, keeper.SafeChainBitcoin, id, []byte{64}) if err != nil { return err } diff --git a/observer/node.go b/observer/node.go index 26e41e5b..b4ba3bfd 100644 --- a/observer/node.go +++ b/observer/node.go @@ -101,7 +101,7 @@ func (node *Node) sendBitcoinPriceInfo(ctx context.Context, chain byte) error { extra = append(extra, uuid.Must(uuid.FromString(asset.AssetId)).Bytes()...) extra = binary.BigEndian.AppendUint64(extra, uint64(amount.IntPart())) extra = binary.BigEndian.AppendUint64(extra, uint64(minimum.IntPart())) - return node.sendBitcoinKeeperResponse(ctx, dummy, common.ActionObserverSetOperationParams, chain, id, extra) + return node.sendKeeperResponse(ctx, dummy, common.ActionObserverSetOperationParams, chain, id, extra) } func (node *Node) snapshotsLoop(ctx context.Context) { @@ -204,7 +204,7 @@ func (node *Node) handleCustomObserverKeyRegistration(ctx context.Context, s *mi id := mixin.UniqueConversationID(observer, observer) extra = append([]byte{common.RequestRoleObserver}, chainCode...) extra = append(extra, common.RequestFlagCustomObserverKey) - err = node.sendBitcoinKeeperResponse(ctx, observer, common.ActionObserverAddKey, keeper.SafeChainBitcoin, id, extra) + err = node.sendKeeperResponse(ctx, observer, common.ActionObserverAddKey, keeper.SafeChainBitcoin, id, extra) if err != nil { return false, err } diff --git a/signer/frost_test.go b/signer/frost_test.go index ed0454a1..7cab2721 100644 --- a/signer/frost_test.go +++ b/signer/frost_test.go @@ -4,13 +4,11 @@ import ( "context" "encoding/hex" "fmt" - "strings" "testing" "time" "github.com/MixinNetwork/mixin/crypto" "github.com/MixinNetwork/mixin/logger" - "github.com/MixinNetwork/multi-party-sig/pkg/party" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/trusted-group/mtg" "github.com/fox-one/mixin-sdk-go" @@ -68,67 +66,31 @@ func testFROSTKeyGen(ctx context.Context, require *require.Assertions, nodes []* return public } -func testFROSTSign(ctx context.Context, require *require.Assertions, nodes []*Node, public string, msg []byte, curve uint8) []byte { - sid := mixin.UniqueConversationID("sign", fmt.Sprintf("%d:%x", curve, msg)) +func testFROSTSign(ctx context.Context, require *require.Assertions, nodes []*Node, public string, msg []byte, crv uint8) []byte { + node := nodes[0] + sid := mixin.UniqueConversationID("sign", fmt.Sprintf("%d:%x", crv, msg)) fingerPath := append(common.Fingerprint(public), []byte{0, 0, 0, 0}...) - network := nodes[0].network.(*testNetwork) - for i := 0; i < 4; i++ { - node := nodes[i] - sop := &common.Operation{ - Type: common.OperationTypeSignInput, - Id: sid, - Curve: curve, - Public: hex.EncodeToString(fingerPath), - Extra: msg, - } - memo := mtg.EncodeMixinExtra("", sid, string(node.encryptOperation(sop))) - out := &mtg.Output{ - AssetID: node.conf.KeeperAssetId, - Memo: memo, - Amount: decimal.NewFromInt(1), - TransactionHash: crypto.NewHash([]byte(sop.Id)), - CreatedAt: time.Now(), - } - - msg := common.MarshalJSONOrPanic(out) - network.mtgChannel(nodes[i].id) <- msg - } - - var extra []byte - for _, node := range nodes { - op := testWaitOperation(ctx, node, sid) - logger.Verbosef("testWaitOperation(%s, %s) => %v\n", node.id, sid, op) - require.Equal(common.OperationTypeSignOutput, int(op.Type)) - require.Equal(sid, op.Id) - require.Equal(curve, op.Curve) - require.Len(op.Public, 64) - require.Len(op.Extra, 64) - extra = op.Extra + sop := &common.Operation{ + Type: common.OperationTypeSignInput, + Id: sid, + Curve: crv, + Public: hex.EncodeToString(fingerPath), + Extra: msg, } - return extra -} - -func testFROSTPrepareKeys(ctx context.Context, require *require.Assertions, nodes []*Node, curve uint8) string { - const public = "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b" - sid := mixin.UniqueConversationID("prepare", public) - for _, node := range nodes { - parts := strings.Split(testFROSTKeys[node.id], ";") - pub, share := parts[0], parts[1] - conf, _ := hex.DecodeString(share) - require.Equal(public, pub) - - op := &common.Operation{Id: sid, Curve: curve, Type: common.OperationTypeKeygenInput} - err := node.store.WriteSessionIfNotExist(ctx, op, crypto.NewHash([]byte(sid)), 0, time.Now(), false) - require.Nil(err) - err = node.store.WriteKeyIfNotExists(ctx, op.Id, curve, pub, conf) - require.Nil(err) + memo := mtg.EncodeMixinExtra("", sid, string(node.encryptOperation(sop))) + out := &mtg.Output{ + AssetID: node.conf.KeeperAssetId, + Memo: memo, + Amount: decimal.NewFromInt(1), + TransactionHash: crypto.NewHash([]byte(sop.Id)), + CreatedAt: time.Now(), } - return public -} + op := TestProcessOutput(ctx, require, nodes, out, sid) -var testFROSTKeys = map[party.ID]string{ - "member-id-0": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d3000020020fe4584dcd16c51736b64e329ef2fd51b4f1d98ee833cdc96ace16398fd243f080020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", - "member-id-1": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d3100020020c6ec44a22c007a43d7518ac10669424693b159534fa32dbe872a5169c8f7210c0020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", - "member-id-2": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d3200020020e6543b705f73a02061f97cdcc45a47934dc5ee9f7a9f382d417eb74128ea100f0020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", - "member-id-3": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d330002002071aa71e94f63b2b232bec3d74a0b05ee7d5857d40531fde3d8dc96211dfc0b010020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", + require.Equal(common.OperationTypeSignOutput, int(op.Type)) + require.Equal(sid, op.Id) + require.Equal(crv, op.Curve) + require.Len(op.Public, 64) + require.Len(op.Extra, 64) + return op.Extra } diff --git a/signer/group.go b/signer/group.go index fd3baae4..7fa2c320 100644 --- a/signer/group.go +++ b/signer/group.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "database/sql" + "encoding/binary" "encoding/hex" "fmt" "slices" @@ -214,9 +215,7 @@ func (node *Node) processSignerResult(ctx context.Context, op *common.Operation, case common.OperationTypeSignInput: if session.State == common.RequestStateInitial && session.PreparedAt.Valid { op := session.asOperation() - extra := []byte{byte(len(op.Extra))} - extra = append(extra, op.Extra...) - extra = append(extra, sig...) + extra := node.concatMessageAndSignature(op.Extra, sig) err = node.store.MarkSessionPending(ctx, op.Id, op.Curve, op.Public, extra) logger.Verbosef("store.MarkSessionPending(%v) => %x %v\n", op, extra, err) if err != nil { @@ -313,12 +312,20 @@ func (node *Node) verifySessionHolder(ctx context.Context, crv byte, holder stri } } +func (node *Node) concatMessageAndSignature(msg, sig []byte) []byte { + extra := binary.BigEndian.AppendUint32(nil, uint32(len(msg))) + extra = append(extra, msg...) + extra = append(extra, sig...) + return extra +} + func (node *Node) verifySessionSignature(ctx context.Context, crv byte, holder string, extra, share, path []byte) (bool, []byte) { - if len(extra) < int(extra[0])+32 { + el := binary.BigEndian.Uint32(extra[:4]) + if len(extra) < int(el)+32 { return false, nil } - msg := extra[1 : 1+extra[0]] - sig := extra[1+extra[0]:] + msg := extra[4 : 4+el] + sig := extra[4+el:] public, _ := node.deriveByPath(ctx, crv, share, path) switch crv { @@ -496,9 +503,7 @@ func (node *Node) startSign(ctx context.Context, op *common.Operation, members [ if err != nil { return node.store.FailSession(ctx, op.Id) } - extra := []byte{byte(len(op.Extra))} - extra = append(extra, op.Extra...) - extra = append(extra, res.Signature...) + extra := node.concatMessageAndSignature(op.Extra, res.Signature) err = node.store.MarkSessionPending(ctx, op.Id, op.Curve, op.Public, extra) logger.Verbosef("store.MarkSessionPending(%v) => %x %v\n", op, extra, err) return err @@ -536,7 +541,11 @@ func (node *Node) readKernelStorageOrPanic(ctx context.Context, stx crypto.Hash) if err != nil { panic(err) } - return v + data, err := common.Base91Decode(string(v)) + if err != nil || len(data) < 32 { + panic(stx.String()) + } + return data } tx, err := common.ReadKernelTransaction(node.conf.MixinRPC, stx) diff --git a/signer/mixin_test.go b/signer/mixin_test.go index 111436d2..ed646803 100644 --- a/signer/mixin_test.go +++ b/signer/mixin_test.go @@ -9,6 +9,7 @@ import ( "github.com/MixinNetwork/mixin/common" "github.com/MixinNetwork/mixin/crypto" "github.com/MixinNetwork/mixin/logger" + sc "github.com/MixinNetwork/safe/common" "github.com/fox-one/mixin-sdk-go" "github.com/stretchr/testify/require" ) @@ -24,7 +25,7 @@ func TestFROSTMixinSign(t *testing.T) { require := require.New(t) ctx, nodes := TestPrepare(require) - public := testFROSTPrepareKeys(ctx, require, nodes, CurveEdwards25519Mixin) + public := TestFROSTPrepareKeys(ctx, require, nodes, CurveEdwards25519Mixin) addr := mixinAddress(public) require.Equal(testMixinAddress, addr.String()) @@ -87,7 +88,7 @@ func writeTransactionReferences(ctx context.Context, nodes []*Node, crv byte, ms func writeStorageTransaction(ctx context.Context, nodes []*Node, extra []byte) []byte { tx := crypto.Blake3Hash(extra) k := hex.EncodeToString(tx[:]) - v := hex.EncodeToString(extra) + v := hex.EncodeToString([]byte(sc.Base91Encode(extra))) for _, n := range nodes { err := n.store.WriteProperty(ctx, k, v) if err != nil { diff --git a/signer/test.go b/signer/test.go index 031564d4..22f09639 100644 --- a/signer/test.go +++ b/signer/test.go @@ -26,6 +26,13 @@ import ( "github.com/stretchr/testify/require" ) +func TestWriteProperty(ctx context.Context, node *Node, k, v string) { + err := node.store.WriteProperty(ctx, k, v) + if err != nil { + panic(err) + } +} + func TestPrepare(require *require.Assertions) (context.Context, []*Node) { logger.SetLevel(logger.VERBOSE) ctx := context.Background() @@ -55,6 +62,24 @@ func TestPrepare(require *require.Assertions) (context.Context, []*Node) { return ctx, nodes } +func TestFROSTPrepareKeys(ctx context.Context, require *require.Assertions, nodes []*Node, curve uint8) string { + const public = "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b" + sid := mixin.UniqueConversationID("prepare", public) + for _, node := range nodes { + parts := strings.Split(testFROSTKeys[node.id], ";") + pub, share := parts[0], parts[1] + conf, _ := hex.DecodeString(share) + require.Equal(public, pub) + + op := &common.Operation{Id: sid, Curve: curve, Type: common.OperationTypeKeygenInput} + err := node.store.WriteSessionIfNotExist(ctx, op, crypto.NewHash([]byte(sid)), 0, time.Now(), false) + require.Nil(err) + err = node.store.WriteKeyIfNotExists(ctx, op.Id, curve, pub, conf) + require.Nil(err) + } + return public +} + func TestCMPPrepareKeys(ctx context.Context, require *require.Assertions, nodes []*Node, crv byte) (string, string) { const public = "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c" const chainCode = "f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2" @@ -126,7 +151,7 @@ func testCMPSignWithPath(ctx context.Context, require *require.Assertions, nodes TransactionHash: crypto.NewHash([]byte(sop.Id)), CreatedAt: time.Now(), } - op := TestCMPProcessOutput(ctx, require, nodes, out, sid) + op := TestProcessOutput(ctx, require, nodes, out, sid) require.Equal(common.OperationTypeSignOutput, int(op.Type)) require.Equal(sid, op.Id) @@ -135,7 +160,7 @@ func testCMPSignWithPath(ctx context.Context, require *require.Assertions, nodes return op.Extra } -func TestCMPProcessOutput(ctx context.Context, require *require.Assertions, nodes []*Node, out *mtg.Output, sessionId string) *common.Operation { +func TestProcessOutput(ctx context.Context, require *require.Assertions, nodes []*Node, out *mtg.Output, sessionId string) *common.Operation { network := nodes[0].network.(*testNetwork) for i := 0; i < 4; i++ { data := common.MarshalJSONOrPanic(out) @@ -292,9 +317,17 @@ func (n *testNetwork) msgChannel(id party.ID) chan []byte { return n.msgChannels[id] } -var testCMPKeys = map[party.ID]string{ - "member-id-0": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d30695468726573686f6c64026545434453415820d2c3785b9befae6fd8dd0dcf08ee21aed1ca3ef69bb0e8fe4f6533e2f26eb2d067456c47616d616c5820036602c3d8c9f7b093b6a7427dfcab8fd278b55c30660571e28efad6b24c033a61505880fd37ff1b459e9cfc3d815e16e009050eeba3f450ac5e72b8fe3ee481ab7a71bb957a463eb7afebb0ff17ccc4fc2d7200773e75a0b286a45183f1869b2010167b5e67d8803490d77a8bb5f49150d8e4bba1b8104a5e6c6447a9ada9b21dc7ddda6886e058430c5958e7232b55001281c1abf64f83395c2053000c8489bb66c83761515880e5e69ba508950a4063916f865447f07834961fe9751351029d9b38eb69da89aedf60b9916da25b114b6154c27981ffc875643a9a0ea11baa71fdd13512fab70798e43ec61c0855baa26262ae383911eb1b242675680f86e8a022539e18fe5641bd5b6e06d6a24a20dc591f27b4c5761282e44e6ce490ea29161589b13da9a4cb635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", - "member-id-1": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d31695468726573686f6c6402654543445341582080f03d8a5df13c452b0e86782d85b29a44176342772e207a29d8a72c4abbbbbb67456c47616d616c5820332e3fe4a572a3a54cf2f4b2e31b5bd0849f8fc3311fa5454f84391232fab42461505880defe6f37274269d7f9de2e3eb6aaef87a79122467be2faf16591015f3e9113fdf7c3a2641a3ec9590e1d4e54f733686f827090e19920db3c71a84dd42b3131383a1a8642d89257525c547eebd43f625c58243990d7682b78561cab57632e3a63a514d024f2da9df17febcd12523347039398f2832986ca3d9c4cb94f45b617fb61515880d951bad3655ae493c51b9183ef03d15f05a66773c88180fb11bdc6eeceb6bd8fdabfd6138f926219c7652bc67da1748595af44b284610af75b49d90a6c97cf88c2eb18886f6123e4be05b753f17ce63be6eb69ece843a260a1e0635eaf5df708f6a60afd1b96105baa16858871fcb7399f0dc48dfde3e790d7e6a544e8ebc067635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", - "member-id-2": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d32695468726573686f6c64026545434453415820f6f534c4308d09ef0e90c087ac47c23763aba85bfc8fb284c1170218b63f140a67456c47616d616c58204be233ba60be43eb4c4fb0838c30e6ee706f0bd8bcb33936c17de5244cd6eaf761505880fc5240ec5d4a9786feef284fad957bb1b8df6a02802cab7fe6975515a0d525a6594d2e8b5dd9d891855e790794b85df7427966a45a83a411bc5151bbfc3eff3d278d44012b99cd8ecc2f6f9cdfb5c65cf27811d6c25f8add34311cd307043c7a8940a7f35621517dfabca0105df9493a770d27860b84a6745bb6f3f17245b26f61515880e01a1996c1e35e79a2c008490347e6eebd61232525a51e9a877ab018189ae39287881a9898b9fc276ec28ca4f0148fcbf05a18dd26a66f0822da1e85ff16f0a208009e3e67ee11949143a4614a7fb83c1f345f4d7c1191a9080a03e004a84d7a4f9d78d6fff74152b0c7033bbbd083ecf9b7b2bf54709e2e7df8d20aa97b48cf635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", - "member-id-3": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d33695468726573686f6c6402654543445341582034d25e0913c3176d8363bbfd85345088bb295475cd445ea6957b878e948c393b67456c47616d616c5820b0f245b285983d7868bb1e075c8a1568e7e36b64c5a6677db73f262fa6fccced61505880cd174d4298454f00298d03d7179326b7043e643077c5404d1386e361485051bc2f52d1b1b681628709a2e70632f027744d8fccb61b3390229a7b6a5b4430834f2e3dc2ea3eff00adc17e3ba3163c53b5acf1307350f490f7432c2b56dfb7861622e5008e7e3eae88a3e29209b62655c2d2a2e063af80b40a88fffd245a13a62761515880dcc2d06407278fa797732c8a46c3c29d78002dcb6d85de1930d47954fd1a18bcad67ac4b562cb20d8445829a5a4bfa1fda16933eaea536298151e6773278e319d89ea717214981014c042ed9b8722667f477f4d086d148960e072d495bb1856b9b68033ac49dc0af525cbd7bb4b398391be71cb8cf58e545714bfda216fb372f635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", -} +var ( + testFROSTKeys = map[party.ID]string{ + "member-id-0": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d3000020020fe4584dcd16c51736b64e329ef2fd51b4f1d98ee833cdc96ace16398fd243f080020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", + "member-id-1": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d3100020020c6ec44a22c007a43d7518ac10669424693b159534fa32dbe872a5169c8f7210c0020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", + "member-id-2": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d3200020020e6543b705f73a02061f97cdcc45a47934dc5ee9f7a9f382d417eb74128ea100f0020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", + "member-id-3": "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b;0001000b6d656d6265722d69642d330002002071aa71e94f63b2b232bec3d74a0b05ee7d5857d40531fde3d8dc96211dfc0b010020fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b000000b9a46b6d656d6265722d69642d305820cd5b764c011927f356938f5ebdd5f825c6f07e72f07a67ab7da1b8ec291de8d56b6d656d6265722d69642d315820d059874222f3d7a00a98da49fe388141717541f7d6ba7b0baf01af63c03510796b6d656d6265722d69642d325820e8b3ba906961e5e2ab66405d7105c2b2c19695a34ae77e229dabc2ef59ec71386b6d656d6265722d69642d33582090115b147e3977a8d44f58d40cdece998bd4b204b02ad91da9756cfff9969298", + } + testCMPKeys = map[party.ID]string{ + "member-id-0": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d30695468726573686f6c64026545434453415820d2c3785b9befae6fd8dd0dcf08ee21aed1ca3ef69bb0e8fe4f6533e2f26eb2d067456c47616d616c5820036602c3d8c9f7b093b6a7427dfcab8fd278b55c30660571e28efad6b24c033a61505880fd37ff1b459e9cfc3d815e16e009050eeba3f450ac5e72b8fe3ee481ab7a71bb957a463eb7afebb0ff17ccc4fc2d7200773e75a0b286a45183f1869b2010167b5e67d8803490d77a8bb5f49150d8e4bba1b8104a5e6c6447a9ada9b21dc7ddda6886e058430c5958e7232b55001281c1abf64f83395c2053000c8489bb66c83761515880e5e69ba508950a4063916f865447f07834961fe9751351029d9b38eb69da89aedf60b9916da25b114b6154c27981ffc875643a9a0ea11baa71fdd13512fab70798e43ec61c0855baa26262ae383911eb1b242675680f86e8a022539e18fe5641bd5b6e06d6a24a20dc591f27b4c5761282e44e6ce490ea29161589b13da9a4cb635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", + "member-id-1": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d31695468726573686f6c6402654543445341582080f03d8a5df13c452b0e86782d85b29a44176342772e207a29d8a72c4abbbbbb67456c47616d616c5820332e3fe4a572a3a54cf2f4b2e31b5bd0849f8fc3311fa5454f84391232fab42461505880defe6f37274269d7f9de2e3eb6aaef87a79122467be2faf16591015f3e9113fdf7c3a2641a3ec9590e1d4e54f733686f827090e19920db3c71a84dd42b3131383a1a8642d89257525c547eebd43f625c58243990d7682b78561cab57632e3a63a514d024f2da9df17febcd12523347039398f2832986ca3d9c4cb94f45b617fb61515880d951bad3655ae493c51b9183ef03d15f05a66773c88180fb11bdc6eeceb6bd8fdabfd6138f926219c7652bc67da1748595af44b284610af75b49d90a6c97cf88c2eb18886f6123e4be05b753f17ce63be6eb69ece843a260a1e0635eaf5df708f6a60afd1b96105baa16858871fcb7399f0dc48dfde3e790d7e6a544e8ebc067635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", + "member-id-2": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d32695468726573686f6c64026545434453415820f6f534c4308d09ef0e90c087ac47c23763aba85bfc8fb284c1170218b63f140a67456c47616d616c58204be233ba60be43eb4c4fb0838c30e6ee706f0bd8bcb33936c17de5244cd6eaf761505880fc5240ec5d4a9786feef284fad957bb1b8df6a02802cab7fe6975515a0d525a6594d2e8b5dd9d891855e790794b85df7427966a45a83a411bc5151bbfc3eff3d278d44012b99cd8ecc2f6f9cdfb5c65cf27811d6c25f8add34311cd307043c7a8940a7f35621517dfabca0105df9493a770d27860b84a6745bb6f3f17245b26f61515880e01a1996c1e35e79a2c008490347e6eebd61232525a51e9a877ab018189ae39287881a9898b9fc276ec28ca4f0148fcbf05a18dd26a66f0822da1e85ff16f0a208009e3e67ee11949143a4614a7fb83c1f345f4d7c1191a9080a03e004a84d7a4f9d78d6fff74152b0c7033bbbd083ecf9b7b2bf54709e2e7df8d20aa97b48cf635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", + "member-id-3": "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c;a96249446b6d656d6265722d69642d33695468726573686f6c6402654543445341582034d25e0913c3176d8363bbfd85345088bb295475cd445ea6957b878e948c393b67456c47616d616c5820b0f245b285983d7868bb1e075c8a1568e7e36b64c5a6677db73f262fa6fccced61505880cd174d4298454f00298d03d7179326b7043e643077c5404d1386e361485051bc2f52d1b1b681628709a2e70632f027744d8fccb61b3390229a7b6a5b4430834f2e3dc2ea3eff00adc17e3ba3163c53b5acf1307350f490f7432c2b56dfb7861622e5008e7e3eae88a3e29209b62655c2d2a2e063af80b40a88fffd245a13a62761515880dcc2d06407278fa797732c8a46c3c29d78002dcb6d85de1930d47954fd1a18bcad67ac4b562cb20d8445829a5a4bfa1fda16933eaea536298151e6773278e319d89ea717214981014c042ed9b8722667f477f4d086d148960e072d495bb1856b9b68033ac49dc0af525cbd7bb4b398391be71cb8cf58e545714bfda216fb372f635249445820b13663e6e4c5053c40bddee742ce959852f89acf55167a3ff0cc66bfa2f5a9f968436861696e4b65795820f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2665075626c696384a66249446b6d656d6265722d69642d30654543445341582102d16b97011927008338404f56d64f2d984fa70699c91f2c8185602721a284e0d767456c47616d616c582102c1f8227a5994d7bf2d996a10489c816845cfe94a424e0e3da4b49e659cbb20b8614e590100e3673176bcd18a5dcb6bb7a8027c71d26efb89205e521a9ee498ac24bbefb3d25b9e12429599fbedc864d2e164c2dfcc00f04014dcd883a2327b095c4f76508c8c06c2841fd273952509c32e7cf727476457d6799d52219fdb3d24e977b5bb1b60f0fc3537c6357777f194b25b8a35afdb04929637a20d9615b32212831c5134456178760251dc3298fbed5f73b0d09489667ce16e87355011bdb6310fef6f2f7020cf5466e7e672fdefffa951cd4207df12d0e8f5bbb5823fc315ae3227433e16086ef89651042b3cdf4028751cfc7c78bd460424cd1787bf69f095c65818a3b08214eeb1c7c37be11eac68d88aa0c0f33d14d7a0f5b5a8192266dbba12ff9d6153590100c4b5aed987e4e31098f21f3184572054acaff47f6003f27cc89529cb2b5de4903a35c54ec9db439391961f256fb64b7fe6fff2bfdeca8a5406f4e78a3bf4e293d0adc698b130d159d35bc48f63fead1170d8f26cd73aa2380ec9c1515ba9febe9153e08bec01f6efd6ac02268c68d58868fbb9b1c1f71788befc995bd0e82d35ab9f0ad6a8cf4ba6319cb6c39a109f99513b5b04fbae532da64583be66ed88bffa1572151e28af9defecea3bd0f2b862dd8820fcc9c5bef12f71309fa23cb10a4a8d5db604c8577e356c83388043bdb0a58279d9de53c214e1dea1e2ad5a47bead3bbd33e71bc763a086140819f308980fc457547afa4c62d4a2601ec24cc2b9615459010029a560c8f3aaea766c9f31e61fd665c65b6ca4e72d1374484a8000d6f57711df03a95fbe1c7ebdc9359b902f08bc48ce06a116be5dc38416298cae1481b19689244016038f7e74bbed78b80afc806fab2b86d7af4d0f57d99ff9f075b0ab654c1314d4444eb006e99ab543b51a701f5972693003703b8dd8e5ad37f8f33e9f09f4f029ea7d3310dc2c00b89c3bd32ce2427306131cacafcf8e399f49756c38012d1f614090d38a1412752c95b08414dc4f8d940526b593720fdbe9a1053e3c7a89c380118fcec394e5c8f38ee6db241d5181eee58f82950a9b4e37a954440994b8a81e839ce9610627f49fdd1a6ec98dd5a4641b7c58b0d06e434d54b7f47339a66249446b6d656d6265722d69642d3165454344534158210205911bbe040e9b1e4cb1276d34f339cf56e2688d7d0e6c1d67df37ea9adf303f67456c47616d616c582102debdda71b628a8e0b8919064c2ba40d1297271281c7b87a2b911fc0ec42787ed614e590100bd4cdd83f18803265b8158f87cbd448c3f329946f04a074cca9cfc5bc898d1b34c44bc69ba124b5921739df3a773aafc64871cfa11e0d466daffe638c9595f5f84980f4b35b571d32387edd2cb59b51cd11bb475ab42482248d788d4176911bc718c37a51eda81d2d73d8e1d0d4f185bea33804ba114a546f76d3e24dede18c2419251bc48e0446ba7714d3327bd12c3e62ae60b130813604ecc240992d8af586c8f9b32c1324779c18b9e1080acb2ccf70e83a09ed57c141762702ce2383161f9bdb6f0ffdbcf3c37df219f301db4da185d6e52e9060b6173dc01a78fc7bc0e0153383e515390d7992a6c828d37c0cbaa306e812a55714b9b212cbd19a8e5fd615359010016c7797854a64e2cd8b75550e6930fa74dd05d1524ab914e96ebe12c755f64afbd39387d4c358fe2a182d7f3fdf4b5a61416cfccf203e5369a358467d94580ba6b4cc98b5bf180101e8787d5e39984fc6235ad2526efb38ef3606654b80ea93c1c35937123c8a43b2cc04873cd6236126d8f17f18f576ce0250d2ae65edccec178729d89945bbf45776f6747b6ef09e3ecf06a4bf5701b847ca9bc3831ad3a62d329ed524a2517035e115e2dbe163102fc215aac49a92b13e2be282964681cef742e36d792d4793500390f38661a4fadfbdabc722fa4ce4db640dc628642706a85b47a7ade8381ddd664a661b23ecf49a89777adea493b6f4a9e68d18a407e4c6154590100287ceae05c8c8add2f575d26d457cee92d76dc643d1e2bb5f485d6e9b4d0180a6691f0ae9e7f4e8d8c6776d2d305acf173e5edaf17287d53148468fce7a2cc3cc7e5675b9d9ab043fcf8adef3698922e8736d781c40992ff836f5ed85cf23a8fc6da79b913845bfe3cabcc72bc1a672bca98b6c70f0aa53414efaf9b544c95a029d965d06c84b3364a4d53aed9462bc1b1da1590a61dbd25b2b437256b3ee6aeb3d662cd36c580e044435f0f4a0109c190091cacf5570d0eb54c5c056c9786f00eda835649dc80decdcbb391fa04ca370ecf86b103574e035b1f6efcab4f9f5aa89fc7711852b0daaf4ca35a72ba2d898929d9bd958b48daaf0e4af6a0eda693a66249446b6d656d6265722d69642d3265454344534158210230627dc5e3d64a959581e44ebc60b593b87517bb501ae8bcfdc7b4b21a2dda2f67456c47616d616c582102179e1b352d573edeca6919748f4b905a015febe7bac04b82885d7e210fceb3ef614e590100dce1b2620945fac45054e1d9f6de47427e85ecf0d6fbbbfc35d3784f5c281d96c434f99a2f784e7a76373680741869c183a7e09931445d78d2f1b602008cbcbd81864066e292dba5627b779d2547bb6451cde23b3748def6dbbc4611c588af1a9963f345140070270c979703646ba3a0b04a49c5a230767e3867431ecc7819f01eec83a6b64c5cd94528c9de143b98b0b3b51ced0702227a9405dfc38d82dde103e565737a17c915a84603c581d3007a1fa31f8e217e562f018a2e870622b3ed16f5205e9af7ea53be9fd29b95f4c3cbe340a2c119be0c5998fb929adfe59a64df1881562647a28bd8ddf2dcef7182895bf7eb2552b20e95e89d18fd02df7fc1615359010004e3ae6b395ce0bb2be593b90d508fbaf7f2953406423631b9dfdc042f043e6c48112dd6073d9d5b381b8e6c719eea5a1db7c85797dbcd1cb140e423ff3ad7dc792dd9177d648c67a3b5c31e3102fba1bf7211f21a9c9e7a719e87d35e1caea0c5bcf13214876c55188d06444c18aa7f8aad0b856ec3e1ec8c80f9db2b705e46874a6ae354c0b3689cf8e9b6ca2f381c9792a00dddce6e5549dae0da5bd85cabcfd4e436216dc1f4d57e45e3d204b2ee72d7f7d27c230045f2bbb418fa32dce4799dbbb6d4929a34cca24e9642ad8df4f8ec09f089f9a76bb024398704c4614c095b51210f8da55467d62f036248ed2c57a4d13ba43e44c3e8d9c65b3010aeaf61545901003f99e748e69cc8238c2150beb9b4d25d3b495db676d75d6e11abe138f9b7a52daf703c21e8546ca35bcf8ef87df77ee4bc096f34472f5deab69bf6be9d1d179e61bbab9b2fb6c041e6c733bd0b8d4a0e6fd94070452c946ec2b695ea9cc5e7fe519b049b250ef692a6be23ef508ed3c98957810c96fa2d4345b9f27debeb5b0e4b705e25f3cc484efb0487831f4b2efb2f4fa8c6473e960f34d57a2f9a0bfa791dc59e2def63712e17b50f5082f58d08b79581b61fa22b5dbf6b0b8c1e5142527f2576b0c81dff96ba02757ffb33b52efc3bfbb1775d2f282ba8cdcb91b20f6f4fc3d4f823ae4349acf4d2d7eafeac49c78beede2d8c70b6b208e9b4c2a859d4a66249446b6d656d6265722d69642d3365454344534158210371f3ab8aa11ebaf368eae875398791ecfac356fe35adb4ef9c727645ed311a9067456c47616d616c582102bb4a36f56b23af462bca403704e0ef456f3409155c5c059efc1d67cea119317b614e590100b0dc1900d8ee92e5ac175eed6b3afa742eaf3ca70d4a77848a55644daed3bdd04525d883aab2ec0aaa294d88e6b3edd421ffa8b627985a480f950b0ac4137f0c69e8968fca187394e4c17bb1804a33d84b2e56862592948aa569b0eb755047a248ba2b3b7284213ea9f667317a5098bab688d1ac7825324a9655c95f829b465781bc499a18f31f5d993acbd9b4efdb91e70084e9afe30e5a142a23ba6e0c069259b49a2a5dc5c6e593d27caf55a33d1dd34219c3da16e79958c2d11f0f5e753cde9ae67c4185e7fa6a695d2251033c38648bf9701d448ba9d4c939ce0c5194d71de5d092196f8f261b58cbe6648152b3262082f9b3527dbec8d4fa40048ae22961535901002a74d1ecf0a86489b4a1382998b58969ef6b5c81985d0587af746ca9893dac867725dbbc34dc7c4455a23a0f5d207294124c1e43ed0e1170f9ce3ea0bba9c18bf7a723cf8faa2c42134f21bf1215ac11e44cfd4233c401b54d2d968fa8cc8d9c80d52a38c328b08387829c9af71144371b9b95e3f0e192ad31cb55fe6a8e6d4f293476264e3fb866171b4a663d5f5bf1fafe2936547f39a46e5d44e6560ffc6bbfdbe82cf29e2389192cfece8d09492a6a036051676e69a7cc9c1a2f1ddf6c92b97516d9e5ab9293e751d120784664f20881715f12934e1116b341364b9191911b53c00ef98fc0bd667deae325446e28d902020e33451ccb76be94eb96bc03f96154590100019e725d7ffe4ce282a4c47e6559e86ca8aaa527a5db6a5f140a3948a1cdba6d891d06b239fe9c99f6d7c1db74ddeb05f8e0bec146aef7cce7133114e38c9f88810c30caa50cc6d3bc6dd01714e0a0d9bbf62bfd5ac301847ffae077ba2874b2586369c2e2345f26da526f3649b0749e60bf0d5f768009ee4d782ab1b489b25da1ce6dc434d201052bab5674c12852b082359f397278a8b339e924fb217348b383f6246247dcbce2b71b350f3cc94fb8c15cbfcbf5faa1951d2fa53f5a531cd4c877b9754d1d4ef0e817685c74e1dc093303fa4a89e910fb0bd8db1cd0b9e95660152a26fef680cdd5082d289da511ba4e278d4c69e8638cda4abe15454c0b77", + } +) From 7decb51e737ee9358ca75e542d4b2a25bad897c9 Mon Sep 17 00:00:00 2001 From: Cedric Fung Date: Tue, 31 Oct 2023 19:38:38 +0000 Subject: [PATCH 2/2] replace some external functions --- cmd/monitor.go | 2 +- go.mod | 34 +++++---- go.sum | 165 ++++++++---------------------------------- messenger/mixin.go | 2 +- observer/bitcoin.go | 25 +++---- observer/ethereum.go | 25 +++---- observer/holder.go | 3 +- observer/key.go | 5 +- observer/node.go | 16 ++-- signer/frost_test.go | 5 +- signer/group.go | 3 +- signer/mixin_test.go | 3 +- signer/node.go | 4 +- signer/signer_test.go | 3 +- signer/test.go | 9 +-- 15 files changed, 96 insertions(+), 208 deletions(-) diff --git a/cmd/monitor.go b/cmd/monitor.go index e6dd3d43..32005793 100644 --- a/cmd/monitor.go +++ b/cmd/monitor.go @@ -204,7 +204,7 @@ func postMessages(ctx context.Context, store UserStore, conv *bot.Conversation, ConversationId: conv.ConversationId, RecipientId: s.UserId, Category: bot.MessageCategoryPlainText, - MessageId: mixin.UniqueConversationID(msg, s.UserId), + MessageId: common.UniqueId(msg, s.UserId), Data: base64.RawURLEncoding.EncodeToString([]byte(msg)), }) } diff --git a/go.mod b/go.mod index 86813c82..5f123188 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/MixinNetwork/safe go 1.21 require ( - github.com/MixinNetwork/bot-api-go-client v1.8.6 + github.com/MixinNetwork/bot-api-go-client v1.8.7 github.com/MixinNetwork/go-number v0.1.0 - github.com/MixinNetwork/mixin v0.16.7 + github.com/MixinNetwork/mixin v0.16.10 github.com/MixinNetwork/multi-party-sig v0.3.2 github.com/MixinNetwork/nfo v0.3.5 github.com/MixinNetwork/trusted-group v0.6.0 @@ -19,6 +19,7 @@ require ( github.com/ethereum/go-ethereum v1.13.4 github.com/fox-one/mixin-sdk-go v1.7.11 github.com/fxamacker/cbor/v2 v2.5.0 + github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid/v5 v5.0.0 github.com/mattn/go-sqlite3 v1.14.17 github.com/mdp/qrterminal v1.0.1 @@ -30,18 +31,19 @@ require ( require ( filippo.io/edwards25519 v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/MixinNetwork/mobilecoin-account v0.0.5 // indirect github.com/MixinNetwork/msgpack/v4 v4.4.0 // indirect github.com/aead/siphash v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/bwesterb/go-ristretto v1.2.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/cronokirby/saferith v0.33.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/blake2b v1.0.0 // indirect @@ -50,25 +52,24 @@ require ( github.com/dgraph-io/badger/v4 v4.2.0 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/ethereum/c-kzg-4844 v0.3.1 // indirect github.com/fox-one/msgpack v1.0.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/glog v1.1.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect github.com/google/uuid v1.3.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/uint256 v1.2.3 // indirect - github.com/kkdai/bstream v1.0.0 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect @@ -85,18 +86,19 @@ require ( github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect - go.opencensus.io v0.24.0 // indirect + go.opencensus.io v0.22.5 // indirect golang.org/x/crypto v0.14.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.58.3 // indirect + golang.org/x/tools v0.14.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/qr v0.2.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/go.sum b/go.sum index 531a5d39..1d633c03 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/MixinNetwork/bot-api-go-client v1.8.6 h1:QzA+yXJuDXpOX+1xvlw76/wtfhQDAa+TA/YXB3XaWK4= -github.com/MixinNetwork/bot-api-go-client v1.8.6/go.mod h1:Phc+juNRPDadsTUfLOeIbYHX6AiePofe8YH3gIxARZ8= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/MixinNetwork/bot-api-go-client v1.8.7 h1:+BMgP0hbZFV+Z77DZ9AYWCXBYYgKuAWf2GrtfkefKsY= +github.com/MixinNetwork/bot-api-go-client v1.8.7/go.mod h1:RJdkxQIdNO0X17SKAoJ8WCSP4QVeDblDulkauSk9ctw= github.com/MixinNetwork/go-number v0.1.0 h1:rxXG00MFa6w+1dyduacZQsbmHPR3duZNSgAXgXtkMBw= github.com/MixinNetwork/go-number v0.1.0/go.mod h1:TASJGXNi4CK9bh2AOQFpVe7r3//CBuZJTNlJYzTXRUk= -github.com/MixinNetwork/mixin v0.16.7 h1:7Wvwrv7DC8ROYlIiG1JkVB/3LDO2nmmmTyUomL09E0Y= -github.com/MixinNetwork/mixin v0.16.7/go.mod h1:uYS7gY5ehpo4nw8zE0pvTEqIbxgZKN7TjZtHE33VavE= +github.com/MixinNetwork/mixin v0.16.10 h1:cTZ7Ic/BFe3HNX7X1raqxsapj/f8117iBCalIC/lXmg= +github.com/MixinNetwork/mixin v0.16.10/go.mod h1:FCNTEGhUoIyV1dYcXqrnjb0ir5iLHSJBnN8fI4AXI4U= github.com/MixinNetwork/mobilecoin-account v0.0.5 h1:1ueD9G/zl4dpObFjfm6WJu0m155YAfOLODfBuTBrXBA= github.com/MixinNetwork/mobilecoin-account v0.0.5/go.mod h1:b5+IefD8Iij5K+IWeVKky5iMMrlFHgyEp8ixOucjYIU= github.com/MixinNetwork/msgpack/v4 v4.4.0 h1:IsMyKqr5sSRqifQXQCJ9z5StjY4ubVA8scbVqpAzoR8= @@ -20,16 +20,10 @@ github.com/MixinNetwork/nfo v0.3.5 h1:2c48HTnCMBiOyjw1/4uxh2UpVHBN6k0WEVbjU5Ualx github.com/MixinNetwork/nfo v0.3.5/go.mod h1:zbWp8mtOjiVahGQBu0qa8t9/ndfob5R6E29JG8Ml26M= github.com/MixinNetwork/trusted-group v0.6.0 h1:ERKB7tDuNUgbaWm6VFlQD2upJnmFRHl/9GiPESEq6bc= github.com/MixinNetwork/trusted-group v0.6.0/go.mod h1:h4C0m/LQI0bHpAitbFTvtzx9WlL9JO+hiLN3UDmQQ2g= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= -github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= @@ -65,33 +59,18 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA= -github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= -github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= -github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo= github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA= @@ -114,27 +93,18 @@ github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8Bzu github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ= github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= -github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.12.2 h1:eGHJ4ij7oyVqUQn48LBz3B7pvQ8sV0wGJiIE6gDq/6Y= -github.com/ethereum/go-ethereum v1.12.2/go.mod h1:1cRAEV+rp/xX0zraSCBnu9Py3HQ+geRMj3HdR+k0wfI= github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fox-one/mixin-sdk-go v1.7.11 h1:S7P7xOqSkpJHYxWaXQsi6R/iAEMsEG4f5oMfa22gDk4= github.com/fox-one/mixin-sdk-go v1.7.11/go.mod h1:5eYOGYrcvZVC4dqtVDL4eKSat77M/g1Du3PMKTJClzE= github.com/fox-one/msgpack v1.0.0 h1:atr4La29WdMPCoddlRAPK2e1yhBJ2cEFF+2X93KY5Vs= @@ -145,10 +115,6 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -156,8 +122,6 @@ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPr github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= @@ -166,14 +130,11 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -188,7 +149,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -201,75 +161,41 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= -github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= -github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= -github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= -github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= -github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -286,19 +212,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= @@ -306,8 +220,6 @@ github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMT github.com/shopspring/decimal v1.2.1-0.20210329231237-501661573f60/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= -github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -316,20 +228,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= -github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= @@ -346,18 +254,12 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= -github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= -go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= -go.dedis.ch/kyber/v3 v3.1.0 h1:ghu+kiRgM5JyD9TJ0hTIxTLQlJBR/ehjWvWwYW3XsC0= -go.dedis.ch/kyber/v3 v3.1.0/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1U= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -368,8 +270,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -377,6 +279,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -391,7 +295,6 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -404,12 +307,12 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -417,6 +320,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -448,14 +352,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -466,6 +367,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -473,21 +376,20 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -503,13 +405,8 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -520,8 +417,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= diff --git a/messenger/mixin.go b/messenger/mixin.go index 45657480..1c71d3fb 100644 --- a/messenger/mixin.go +++ b/messenger/mixin.go @@ -233,5 +233,5 @@ func (mm *MixinMessenger) sendMessagesWithoutTimeout(ctx context.Context, batch func uniqueMessageId(receiver string, b []byte) string { s := hex.EncodeToString(b) - return mixin.UniqueConversationID(receiver, s) + return common.UniqueId(receiver, s) } diff --git a/observer/bitcoin.go b/observer/bitcoin.go index f0018069..84ac7b11 100644 --- a/observer/bitcoin.go +++ b/observer/bitcoin.go @@ -19,7 +19,6 @@ import ( "github.com/MixinNetwork/safe/keeper/store" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" - "github.com/fox-one/mixin-sdk-go" "github.com/gofrs/uuid" "github.com/shopspring/decimal" ) @@ -99,8 +98,8 @@ func (node *Node) bitcoinNetworkInfoLoop(ctx context.Context, chain byte) { extra = binary.BigEndian.AppendUint64(extra, uint64(fvb)) extra = binary.BigEndian.AppendUint64(extra, uint64(height)) extra = append(extra, hash[:]...) - id := mixin.UniqueConversationID(assetId, fmt.Sprintf("%s:%d", blockHash, height)) - id = mixin.UniqueConversationID(id, fmt.Sprintf("%d:%d", time.Now().UnixNano(), fvb)) + id := common.UniqueId(assetId, fmt.Sprintf("%s:%d", blockHash, height)) + id = common.UniqueId(id, fmt.Sprintf("%d:%d", time.Now().UnixNano(), fvb)) logger.Printf("node.bitcoinNetworkInfoLoop(%d) => %d %d %s %s", chain, height, fvb, blockHash, id) dummy := node.bitcoinDummyHolder() @@ -296,8 +295,8 @@ func (node *Node) bitcoinConfirmPendingDeposit(ctx context.Context, deposit *Dep } extra := deposit.encodeKeeperExtra() - id := mixin.UniqueConversationID(assetId, deposit.Holder) - id = mixin.UniqueConversationID(id, fmt.Sprintf("%s:%d", deposit.TransactionHash, deposit.OutputIndex)) + id := common.UniqueId(assetId, deposit.Holder) + id = common.UniqueId(id, fmt.Sprintf("%s:%d", deposit.TransactionHash, deposit.OutputIndex)) err = node.sendKeeperResponse(ctx, deposit.Holder, deposit.Category, deposit.Chain, id, extra) if err != nil { return fmt.Errorf("node.sendKeeperResponse(%s) => %v", id, err) @@ -508,12 +507,12 @@ func (node *Node) sendToKeeperBitcoinApproveTransaction(ctx context.Context, app panic(approval.RawTransaction) } - rawId := mixin.UniqueConversationID(approval.RawTransaction, approval.RawTransaction) + rawId := common.UniqueId(approval.RawTransaction, approval.RawTransaction) raw := common.DecodeHexOrPanic(approval.RawTransaction) raw = append(uuid.Must(uuid.FromString(rawId)).Bytes(), raw...) raw = common.AESEncrypt(node.aesKey[:], raw, rawId) msg := base64.RawURLEncoding.EncodeToString(raw) - traceId := mixin.UniqueConversationID(msg, msg) + traceId := common.UniqueId(msg, msg) conf := node.conf.App rs, err := common.CreateObjectUntilSufficient(ctx, msg, traceId, conf.ClientId, conf.SessionId, conf.PrivateKey, conf.PIN, conf.PinToken) if err != nil { @@ -528,7 +527,7 @@ func (node *Node) sendToKeeperBitcoinApproveTransaction(ctx context.Context, app if err != nil { return err } - id := mixin.UniqueConversationID(approval.TransactionHash, approval.TransactionHash) + id := common.UniqueId(approval.TransactionHash, approval.TransactionHash) rid := uuid.Must(uuid.FromString(tx.RequestId)) extra := append(rid.Bytes(), ref[:]...) references := []crypto.Hash{ref} @@ -542,7 +541,7 @@ func (node *Node) sendToKeeperBitcoinApproveTransaction(ctx context.Context, app if approval.UpdatedAt.Add(keeper.SafeSignatureTimeout).After(time.Now()) { return nil } - id = mixin.UniqueConversationID(id, approval.UpdatedAt.String()) + id = common.UniqueId(id, approval.UpdatedAt.String()) err = node.sendKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) logger.Printf("node.sendKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) if err != nil { @@ -788,7 +787,7 @@ func (node *Node) httpSignBitcoinAccountRecoveryRequest(ctx context.Context, saf } var extra []byte - id := mixin.UniqueConversationID(safe.Address, receiver) + id := common.UniqueId(safe.Address, receiver) switch { case !isHolderSigned: // Close account with safeBTC tx, err := node.keeperStore.ReadTransaction(ctx, hash) @@ -809,11 +808,11 @@ func (node *Node) httpSignBitcoinAccountRecoveryRequest(ctx context.Context, saf } objectRaw := signedRaw - rawId := mixin.UniqueConversationID(raw, raw) + rawId := common.UniqueId(raw, raw) objectRaw = append(uuid.Must(uuid.FromString(rawId)).Bytes(), objectRaw...) objectRaw = common.AESEncrypt(node.aesKey[:], objectRaw, rawId) msg := base64.RawURLEncoding.EncodeToString(objectRaw) - traceId := mixin.UniqueConversationID(msg, msg) + traceId := common.UniqueId(msg, msg) conf := node.conf.App rs, err := common.CreateObjectUntilSufficient(ctx, msg, traceId, conf.ClientId, conf.SessionId, conf.PrivateKey, conf.PIN, conf.PinToken) logger.Printf("common.CreateObjectUntilSufficient(%v) => %v %v", msg, rs, err) @@ -930,7 +929,7 @@ func (node *Node) httpRevokeBitcoinTransaction(ctx context.Context, txHash strin } } - id := mixin.UniqueConversationID(approval.TransactionHash, approval.TransactionHash) + id := common.UniqueId(approval.TransactionHash, approval.TransactionHash) rid := uuid.Must(uuid.FromString(tx.RequestId)) extra := append(rid.Bytes(), sig...) action := common.ActionBitcoinSafeRevokeTransaction diff --git a/observer/ethereum.go b/observer/ethereum.go index a6670cfb..c12d3a04 100644 --- a/observer/ethereum.go +++ b/observer/ethereum.go @@ -18,7 +18,6 @@ import ( "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/keeper" "github.com/MixinNetwork/safe/keeper/store" - "github.com/fox-one/mixin-sdk-go" "github.com/gofrs/uuid" "github.com/shopspring/decimal" ) @@ -150,8 +149,8 @@ func (node *Node) ethereumNetworkInfoLoop(ctx context.Context, chain byte) { extra = binary.BigEndian.AppendUint64(extra, gasPrice.Uint64()) extra = binary.BigEndian.AppendUint64(extra, uint64(height)) extra = append(extra, hash[:]...) - id := mixin.UniqueConversationID(assetId, fmt.Sprintf("%s:%d", blockHash, height)) - id = mixin.UniqueConversationID(id, fmt.Sprintf("%d:%d", time.Now().UnixNano(), gasPrice.Uint64())) + id := common.UniqueId(assetId, fmt.Sprintf("%s:%d", blockHash, height)) + id = common.UniqueId(id, fmt.Sprintf("%d:%d", time.Now().UnixNano(), gasPrice.Uint64())) logger.Printf("node.ethereumNetworkInfoLoop(%d) => %d %d %s %s", chain, height, gasPrice.Uint64(), blockHash, id) dummy := node.bitcoinDummyHolder() @@ -284,8 +283,8 @@ func (node *Node) ethereumConfirmPendingDeposit(ctx context.Context, deposit *De } extra := deposit.encodeKeeperExtra() - id := mixin.UniqueConversationID(assetId, deposit.Holder) - id = mixin.UniqueConversationID(id, fmt.Sprintf("%s:%d", deposit.TransactionHash, deposit.OutputIndex)) + id := common.UniqueId(assetId, deposit.Holder) + id = common.UniqueId(id, fmt.Sprintf("%s:%d", deposit.TransactionHash, deposit.OutputIndex)) err = node.sendKeeperResponse(ctx, deposit.Holder, deposit.Category, deposit.Chain, id, extra) if err != nil { return fmt.Errorf("node.sendKeeperResponse(%s) => %v", id, err) @@ -511,12 +510,12 @@ func (node *Node) sendToKeeperEthereumApproveTransaction(ctx context.Context, ap panic(approval.RawTransaction) } - rawId := mixin.UniqueConversationID(approval.RawTransaction, approval.RawTransaction) + rawId := common.UniqueId(approval.RawTransaction, approval.RawTransaction) raw := common.DecodeHexOrPanic(approval.RawTransaction) raw = append(uuid.Must(uuid.FromString(rawId)).Bytes(), raw...) raw = common.AESEncrypt(node.aesKey[:], raw, rawId) msg := base64.RawURLEncoding.EncodeToString(raw) - traceId := mixin.UniqueConversationID(msg, msg) + traceId := common.UniqueId(msg, msg) conf := node.conf.App rs, err := common.CreateObjectUntilSufficient(ctx, msg, traceId, conf.ClientId, conf.SessionId, conf.PrivateKey, conf.PIN, conf.PinToken) if err != nil { @@ -531,7 +530,7 @@ func (node *Node) sendToKeeperEthereumApproveTransaction(ctx context.Context, ap if err != nil { return err } - id := mixin.UniqueConversationID(approval.TransactionHash, approval.TransactionHash) + id := common.UniqueId(approval.TransactionHash, approval.TransactionHash) rid := uuid.Must(uuid.FromString(tx.RequestId)) extra := append(rid.Bytes(), ref[:]...) references := []crypto.Hash{ref} @@ -545,7 +544,7 @@ func (node *Node) sendToKeeperEthereumApproveTransaction(ctx context.Context, ap if approval.UpdatedAt.Add(keeper.SafeSignatureTimeout).After(time.Now()) { return nil } - id = mixin.UniqueConversationID(id, approval.UpdatedAt.String()) + id = common.UniqueId(id, approval.UpdatedAt.String()) err = node.sendKeeperResponseWithReferences(ctx, tx.Holder, byte(action), approval.Chain, id, extra, references) logger.Printf("node.sendKeeperResponseWithReferences(%s, %d, %s, %x, %s)", tx.Holder, action, id, extra, ref) if err != nil { @@ -765,7 +764,7 @@ func (node *Node) httpSignEthereumAccountRecoveryRequest(ctx context.Context, sa } var extra []byte - id := mixin.UniqueConversationID(safe.Address, st.Destination.Hex()) + id := common.UniqueId(safe.Address, st.Destination.Hex()) switch { case !isHolderSigned: // Close account with safeBTC tx, err := node.keeperStore.ReadTransaction(ctx, hash) @@ -785,11 +784,11 @@ func (node *Node) httpSignEthereumAccountRecoveryRequest(ctx context.Context, sa } objectRaw := signedRaw - rawId := mixin.UniqueConversationID(raw, raw) + rawId := common.UniqueId(raw, raw) objectRaw = append(uuid.Must(uuid.FromString(rawId)).Bytes(), objectRaw...) objectRaw = common.AESEncrypt(node.aesKey[:], objectRaw, rawId) msg := base64.RawURLEncoding.EncodeToString(objectRaw) - traceId := mixin.UniqueConversationID(msg, msg) + traceId := common.UniqueId(msg, msg) conf := node.conf.App rs, err := common.CreateObjectUntilSufficient(ctx, msg, traceId, conf.ClientId, conf.SessionId, conf.PrivateKey, conf.PIN, conf.PinToken) logger.Printf("common.CreateObjectUntilSufficient(%v) => %v %v", msg, rs, err) @@ -900,7 +899,7 @@ func (node *Node) httpRevokeEthereumTransaction(ctx context.Context, txHash stri } } - id := mixin.UniqueConversationID(approval.TransactionHash, approval.TransactionHash) + id := common.UniqueId(approval.TransactionHash, approval.TransactionHash) rid := uuid.Must(uuid.FromString(tx.RequestId)) extra := append(rid.Bytes(), sig...) action := common.ActionEthereumSafeRevokeTransaction diff --git a/observer/holder.go b/observer/holder.go index b4b8394b..7068c820 100644 --- a/observer/holder.go +++ b/observer/holder.go @@ -16,7 +16,6 @@ import ( "github.com/MixinNetwork/safe/apps/ethereum" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/keeper" - "github.com/fox-one/mixin-sdk-go" "github.com/gofrs/uuid/v5" ) @@ -163,7 +162,7 @@ func (node *Node) httpApproveSafeAccount(ctx context.Context, addr, sigBase64 st return fmt.Errorf("HTTP: %d", http.StatusNotAcceptable) } - id := mixin.UniqueConversationID(addr, sigBase64) + id := common.UniqueId(addr, sigBase64) rid := uuid.Must(uuid.FromString(sp.RequestId)) extra := append(rid.Bytes(), sig...) return node.sendKeeperResponse(ctx, sp.Holder, byte(action), sp.Chain, id, extra) diff --git a/observer/key.go b/observer/key.go index 8f77f8c3..64e57321 100644 --- a/observer/key.go +++ b/observer/key.go @@ -7,7 +7,6 @@ import ( "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/safe/keeper" - "github.com/fox-one/mixin-sdk-go" ) func (node *Node) safeKeyLoop(ctx context.Context, chain byte) { @@ -46,7 +45,7 @@ func (node *Node) safeAddObserverKeys(ctx context.Context, chain byte) error { if observer == "" { return nil } - id := mixin.UniqueConversationID(observer, observer) + id := common.UniqueId(observer, observer) extra := append([]byte{common.RequestRoleObserver}, chainCode...) extra = append(extra, common.RequestFlagNone) err = node.sendKeeperResponse(ctx, observer, common.ActionObserverAddKey, chain, id, extra) @@ -79,7 +78,7 @@ func (node *Node) safeRequestSignerKeys(ctx context.Context, chain byte) error { return err } dummy := node.bitcoinDummyHolder() - id := mixin.UniqueConversationID(requested.String(), requested.String()) + id := common.UniqueId(requested.String(), requested.String()) err = node.sendKeeperResponse(ctx, dummy, common.ActionObserverRequestSignerKeys, chain, id, []byte{64}) if err != nil { return err diff --git a/observer/node.go b/observer/node.go index a3dd4322..5f81448c 100644 --- a/observer/node.go +++ b/observer/node.go @@ -117,11 +117,11 @@ func (node *Node) sendPriceInfo(ctx context.Context, chain byte) error { panic(node.conf.TransactionMinimum) } dummy := node.bitcoinDummyHolder() - id := mixin.UniqueConversationID("ActionObserverSetOperationParams", dummy) - id = mixin.UniqueConversationID(id, assetId) - id = mixin.UniqueConversationID(id, asset.AssetId) - id = mixin.UniqueConversationID(id, amount.String()) - id = mixin.UniqueConversationID(id, minimum.String()) + id := common.UniqueId("ActionObserverSetOperationParams", dummy) + id = common.UniqueId(id, assetId) + id = common.UniqueId(id, asset.AssetId) + id = common.UniqueId(id, amount.String()) + id = common.UniqueId(id, minimum.String()) extra := []byte{chain} extra = append(extra, uuid.Must(uuid.FromString(asset.AssetId)).Bytes()...) extra = binary.BigEndian.AppendUint64(extra, uint64(amount.IntPart())) @@ -229,7 +229,7 @@ func (node *Node) handleCustomObserverKeyRegistration(ctx context.Context, s *mi } chain := keeper.SafeCurveChain(extra[0]) - id := mixin.UniqueConversationID(observer, observer) + id := common.UniqueId(observer, observer) extra = append([]byte{common.RequestRoleObserver}, chainCode...) extra = append(extra, common.RequestFlagCustomObserverKey) err = node.sendKeeperResponse(ctx, observer, common.ActionObserverAddKey, chain, id, extra) @@ -364,9 +364,9 @@ func (node *Node) writeSnapshotsCheckpoint(ctx context.Context, offset time.Time } func (node *Node) safeTraceId(params ...string) string { - traceId := mixin.UniqueConversationID(node.conf.PrivateKey, node.conf.PrivateKey) + traceId := common.UniqueId(node.conf.PrivateKey, node.conf.PrivateKey) for _, id := range params { - traceId = mixin.UniqueConversationID(traceId, id) + traceId = common.UniqueId(traceId, id) } return traceId } diff --git a/signer/frost_test.go b/signer/frost_test.go index 7cab2721..958a6b54 100644 --- a/signer/frost_test.go +++ b/signer/frost_test.go @@ -11,7 +11,6 @@ import ( "github.com/MixinNetwork/mixin/logger" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/trusted-group/mtg" - "github.com/fox-one/mixin-sdk-go" "github.com/shopspring/decimal" "github.com/stretchr/testify/require" ) @@ -28,7 +27,7 @@ func TestFROSTSigner(t *testing.T) { } func testFROSTKeyGen(ctx context.Context, require *require.Assertions, nodes []*Node, curve uint8) string { - sid := mixin.UniqueConversationID("keygen", fmt.Sprint(curve)) + sid := common.UniqueId("keygen", fmt.Sprint(curve)) for i := 0; i < 4; i++ { node := nodes[i] op := &common.Operation{ @@ -68,7 +67,7 @@ func testFROSTKeyGen(ctx context.Context, require *require.Assertions, nodes []* func testFROSTSign(ctx context.Context, require *require.Assertions, nodes []*Node, public string, msg []byte, crv uint8) []byte { node := nodes[0] - sid := mixin.UniqueConversationID("sign", fmt.Sprintf("%d:%x", crv, msg)) + sid := common.UniqueId("sign", fmt.Sprintf("%d:%x", crv, msg)) fingerPath := append(common.Fingerprint(public), []byte{0, 0, 0, 0}...) sop := &common.Operation{ Type: common.OperationTypeSignInput, diff --git a/signer/group.go b/signer/group.go index 2465e572..83f24a35 100644 --- a/signer/group.go +++ b/signer/group.go @@ -22,7 +22,6 @@ import ( "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/trusted-group/mtg" "github.com/decred/dcrd/dcrec/secp256k1/v4" - "github.com/fox-one/mixin-sdk-go" "github.com/shopspring/decimal" ) @@ -652,7 +651,7 @@ func (node *Node) buildKeeperTransaction(ctx context.Context, op *common.Operati members := node.keeper.Genesis.Members threshold := node.keeper.Genesis.Threshold - traceId := mixin.UniqueConversationID(node.group.GenesisId(), op.Id) + traceId := common.UniqueId(node.group.GenesisId(), op.Id) err := node.group.BuildTransaction(ctx, node.conf.KeeperAssetId, members, threshold, "1", string(extra), traceId, "") logger.Printf("node.buildKeeperTransaction(%v) => %s %x %v", op, traceId, extra, err) return err diff --git a/signer/mixin_test.go b/signer/mixin_test.go index ed646803..4667ec54 100644 --- a/signer/mixin_test.go +++ b/signer/mixin_test.go @@ -10,7 +10,6 @@ import ( "github.com/MixinNetwork/mixin/crypto" "github.com/MixinNetwork/mixin/logger" sc "github.com/MixinNetwork/safe/common" - "github.com/fox-one/mixin-sdk-go" "github.com/stretchr/testify/require" ) @@ -73,7 +72,7 @@ func TestFROSTMixinSign(t *testing.T) { } func writeTransactionReferences(ctx context.Context, nodes []*Node, crv byte, msg, ref []byte) { - sid := mixin.UniqueConversationID("sign", fmt.Sprintf("%d:%x", crv, msg)) + sid := sc.UniqueId("sign", fmt.Sprintf("%d:%x", crv, msg)) hash := crypto.NewHash([]byte(sid)) k := hex.EncodeToString(hash[:]) v := hex.EncodeToString(ref) diff --git a/signer/node.go b/signer/node.go index c72c563a..f83a587f 100644 --- a/signer/node.go +++ b/signer/node.go @@ -498,7 +498,7 @@ func (node *Node) sendSignerPrepareTransaction(ctx context.Context, op *common.O members := node.conf.MTG.Genesis.Members threshold := node.conf.MTG.Genesis.Threshold traceId := fmt.Sprintf("SESSION:%s:SIGNER:%s:PREPARE", op.Id, string(node.id)) - traceId = mixin.UniqueConversationID(traceId, fmt.Sprintf("MTG:%v:%d", members, threshold)) + traceId = common.UniqueId(traceId, fmt.Sprintf("MTG:%v:%d", members, threshold)) err := node.sendTransactionUntilSufficient(ctx, node.conf.AssetId, members, threshold, decimal.NewFromInt(1), extra, traceId) logger.Printf("node.sendSignerPrepareTransaction(%v) => %s %x %v", op, op.Id, extra, err) return err @@ -512,7 +512,7 @@ func (node *Node) sendSignerResultTransaction(ctx context.Context, op *common.Op members := node.conf.MTG.Genesis.Members threshold := node.conf.MTG.Genesis.Threshold traceId := fmt.Sprintf("SESSION:%s:SIGNER:%s:RESULT", op.Id, string(node.id)) - traceId = mixin.UniqueConversationID(traceId, fmt.Sprintf("MTG:%v:%d", members, threshold)) + traceId = common.UniqueId(traceId, fmt.Sprintf("MTG:%v:%d", members, threshold)) err := node.sendTransactionUntilSufficient(ctx, node.conf.AssetId, members, threshold, decimal.NewFromInt(1), extra, traceId) logger.Printf("node.sendSignerResultTransaction(%v) => %s %x %v", op, op.Id, extra, err) return err diff --git a/signer/signer_test.go b/signer/signer_test.go index deef8380..83f68599 100644 --- a/signer/signer_test.go +++ b/signer/signer_test.go @@ -15,7 +15,6 @@ import ( "github.com/MixinNetwork/safe/apps/bitcoin" "github.com/MixinNetwork/safe/common" "github.com/MixinNetwork/trusted-group/mtg" - "github.com/fox-one/mixin-sdk-go" "github.com/shopspring/decimal" "github.com/stretchr/testify/require" ) @@ -81,7 +80,7 @@ func TestSSID(t *testing.T) { } func testCMPKeyGen(ctx context.Context, require *require.Assertions, nodes []*Node, crv byte) (string, []byte) { - sid := mixin.UniqueConversationID("keygen", fmt.Sprint(400)) + sid := common.UniqueId("keygen", fmt.Sprint(400)) for i := 0; i < 4; i++ { node := nodes[i] op := &common.Operation{ diff --git a/signer/test.go b/signer/test.go index 22f09639..580f9b2b 100644 --- a/signer/test.go +++ b/signer/test.go @@ -20,7 +20,6 @@ import ( "github.com/MixinNetwork/safe/saver" "github.com/MixinNetwork/trusted-group/mtg" "github.com/btcsuite/btcd/btcutil/hdkeychain" - "github.com/fox-one/mixin-sdk-go" "github.com/pelletier/go-toml" "github.com/shopspring/decimal" "github.com/stretchr/testify/require" @@ -64,7 +63,7 @@ func TestPrepare(require *require.Assertions) (context.Context, []*Node) { func TestFROSTPrepareKeys(ctx context.Context, require *require.Assertions, nodes []*Node, curve uint8) string { const public = "fb17b60698d36d45bc624c8e210b4c845233c99a7ae312a27e883a8aa8444b9b" - sid := mixin.UniqueConversationID("prepare", public) + sid := common.UniqueId("prepare", public) for _, node := range nodes { parts := strings.Split(testFROSTKeys[node.id], ";") pub, share := parts[0], parts[1] @@ -83,7 +82,7 @@ func TestFROSTPrepareKeys(ctx context.Context, require *require.Assertions, node func TestCMPPrepareKeys(ctx context.Context, require *require.Assertions, nodes []*Node, crv byte) (string, string) { const public = "02bf0a7fa4b7905a0de5ab60a5322529e1a591ddd1ee53df82e751e8adb4bed08c" const chainCode = "f555b08a9871213c0d52fee12e1bd365990b956880491b2b1a106f84584aa3a2" - sid := mixin.UniqueConversationID("prepare", public) + sid := common.UniqueId("prepare", public) for _, node := range nodes { parts := strings.Split(testCMPKeys[node.id], ";") pub, share := parts[0], parts[1] @@ -133,8 +132,8 @@ func testCMPSign(ctx context.Context, require *require.Assertions, nodes []*Node func testCMPSignWithPath(ctx context.Context, require *require.Assertions, nodes []*Node, public string, msg []byte, crv byte, path []byte) []byte { node := nodes[0] - sid := mixin.UniqueConversationID("sign", hex.EncodeToString(msg)) - sid = mixin.UniqueConversationID(sid, hex.EncodeToString(path)) + sid := common.UniqueId("sign", hex.EncodeToString(msg)) + sid = common.UniqueId(sid, hex.EncodeToString(path)) fingerPath := append(common.Fingerprint(public), path...) sop := &common.Operation{ Type: common.OperationTypeSignInput,