Commit 6e5bf601 authored by madengji's avatar madengji Committed by vipwzw

all circuit pass

parent d5900775
...@@ -11,6 +11,7 @@ secret, authorizePriKey,17822967620457187568904804290291537271142779717280482398 ...@@ -11,6 +11,7 @@ secret, authorizePriKey,17822967620457187568904804290291537271142779717280482398
secret, spendFlag,1 secret, spendFlag,1
secret, noteRandom,2824204835 secret, noteRandom,2824204835
secret, noteHash,3649361563603415612447884923592927672484439983354650489908367088830561161361
secret, path1,19561523370160677851616596032513161448778901506614020103852017946679781620105 secret, path1,19561523370160677851616596032513161448778901506614020103852017946679781620105
secret, path2,13898857070666440684265042188056372750257678232709763835292910585848522658637 secret, path2,13898857070666440684265042188056372750257678232709763835292910585848522658637
secret, path3,15019169196974879571470243100379529757970866395477207575033769902587972032431 secret, path3,15019169196974879571470243100379529757970866395477207575033769902587972032431
......
...@@ -5,7 +5,10 @@ ...@@ -5,7 +5,10 @@
package commands package commands
import ( import (
"encoding/hex"
"encoding/json"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/33cn/chain33/rpc/jsonclient" "github.com/33cn/chain33/rpc/jsonclient"
...@@ -829,6 +832,7 @@ func SecretCmd() *cobra.Command { ...@@ -829,6 +832,7 @@ func SecretCmd() *cobra.Command {
Short: "note secret cmd", Short: "note secret cmd",
} }
cmd.AddCommand(EncodeSecretDataCmd()) cmd.AddCommand(EncodeSecretDataCmd())
cmd.AddCommand(DecodeSecretDataCmd())
cmd.AddCommand(EncryptSecretDataCmd()) cmd.AddCommand(EncryptSecretDataCmd())
cmd.AddCommand(DecryptSecretDataCmd()) cmd.AddCommand(DecryptSecretDataCmd())
...@@ -878,6 +882,47 @@ func encodeSecret(cmd *cobra.Command, args []string) { ...@@ -878,6 +882,47 @@ func encodeSecret(cmd *cobra.Command, args []string) {
ctx.Run() ctx.Run()
} }
// EncodeSecretDataCmd get para chain status by height
func DecodeSecretDataCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "decode",
Short: "decode secret data",
Run: decodeSecret,
}
decodeSecretCmdFlags(cmd)
return cmd
}
func decodeSecretCmdFlags(cmd *cobra.Command) {
cmd.Flags().StringP("data", "d", "", "receiver data")
cmd.MarkFlagRequired("data")
}
func decodeSecret(cmd *cobra.Command, args []string) {
//rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
data, _ := cmd.Flags().GetString("data")
var secret mixTy.DHSecret
d, err := hex.DecodeString(data)
if err != nil {
fmt.Println("decode string fail")
return
}
err = types.Decode(d, &secret)
if err != nil {
fmt.Println("decode data fail")
return
}
rst, err := json.MarshalIndent(secret, "", " ")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(string(rst))
}
// ShowAccountPrivacyInfo get para chain status by height // ShowAccountPrivacyInfo get para chain status by height
func EncryptSecretDataCmd() *cobra.Command { func EncryptSecretDataCmd() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
......
...@@ -63,7 +63,7 @@ func (m *Mix) CheckTx(tx *types.Transaction, index int) error { ...@@ -63,7 +63,7 @@ func (m *Mix) CheckTx(tx *types.Transaction, index int) error {
// mix隐私交易,只私对私需要特殊签名验证 // mix隐私交易,只私对私需要特殊签名验证
return m.DriverBase.CheckTx(tx, index) return m.DriverBase.CheckTx(tx, index)
} }
_, _, err := MixTransferInfoVerify(m.GetAPI().GetConfig(), m.GetStateDB(), action.GetTransfer()) _, _, err := MixTransferInfoVerify(m.GetStateDB(), action.GetTransfer())
if err != nil { if err != nil {
mlog.Error("checkTx", "err", err, "txhash", common.ToHex(tx.Hash())) mlog.Error("checkTx", "err", err, "txhash", common.ToHex(tx.Hash()))
return err return err
......
...@@ -7,8 +7,6 @@ package executor ...@@ -7,8 +7,6 @@ package executor
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
mixTy "github.com/33cn/plugin/plugin/dapp/mix/types" mixTy "github.com/33cn/plugin/plugin/dapp/mix/types"
"github.com/consensys/gurvy/bn256/twistededwards" "github.com/consensys/gurvy/bn256/twistededwards"
...@@ -72,7 +70,7 @@ func transferOutputVerify(db dbm.KV, proof *mixTy.ZkProofInfo) (*mixTy.TransferO ...@@ -72,7 +70,7 @@ func transferOutputVerify(db dbm.KV, proof *mixTy.ZkProofInfo) (*mixTy.TransferO
} }
func VerifyCommitValues(inputs []*mixTy.TransferInputPublicInput, outputs []*mixTy.TransferOutputPublicInput, minFee int64) bool { func VerifyCommitValues(inputs []*mixTy.TransferInputPublicInput, outputs []*mixTy.TransferOutputPublicInput) bool {
var inputPoints, outputPoints []*twistededwards.Point var inputPoints, outputPoints []*twistededwards.Point
for _, in := range inputs { for _, in := range inputs {
var p twistededwards.Point var p twistededwards.Point
...@@ -90,7 +88,7 @@ func VerifyCommitValues(inputs []*mixTy.TransferInputPublicInput, outputs []*mix ...@@ -90,7 +88,7 @@ func VerifyCommitValues(inputs []*mixTy.TransferInputPublicInput, outputs []*mix
//out value add fee //out value add fee
//对于平行链来说, 隐私交易需要一个公共账户扣主链的手续费,隐私交易只需要扣平行链执行器内的费用即可 //对于平行链来说, 隐私交易需要一个公共账户扣主链的手续费,隐私交易只需要扣平行链执行器内的费用即可
//由于平行链的隐私交易没有实际扣平行链mix合约的手续费,平行链Mix合约会有手续费留下,平行链隐私可以考虑手续费为0 //由于平行链的隐私交易没有实际扣平行链mix合约的手续费,平行链Mix合约会有手续费留下,平行链隐私可以考虑手续费为0
outputPoints = append(outputPoints, mixTy.MulCurvePointG(uint64(minFee))) outputPoints = append(outputPoints, mixTy.MulCurvePointG(uint64(mixTy.Privacy2PrivacyTxFee)))
//sum input and output //sum input and output
sumInput := inputPoints[0] sumInput := inputPoints[0]
...@@ -108,7 +106,7 @@ func VerifyCommitValues(inputs []*mixTy.TransferInputPublicInput, outputs []*mix ...@@ -108,7 +106,7 @@ func VerifyCommitValues(inputs []*mixTy.TransferInputPublicInput, outputs []*mix
return false return false
} }
func MixTransferInfoVerify(cfg *types.Chain33Config, db dbm.KV, transfer *mixTy.MixTransferAction) ([]*mixTy.TransferInputPublicInput, []*mixTy.TransferOutputPublicInput, error) { func MixTransferInfoVerify(db dbm.KV, transfer *mixTy.MixTransferAction) ([]*mixTy.TransferInputPublicInput, []*mixTy.TransferOutputPublicInput, error) {
var inputs []*mixTy.TransferInputPublicInput var inputs []*mixTy.TransferInputPublicInput
var outputs []*mixTy.TransferOutputPublicInput var outputs []*mixTy.TransferOutputPublicInput
...@@ -129,8 +127,7 @@ func MixTransferInfoVerify(cfg *types.Chain33Config, db dbm.KV, transfer *mixTy. ...@@ -129,8 +127,7 @@ func MixTransferInfoVerify(cfg *types.Chain33Config, db dbm.KV, transfer *mixTy.
} }
outputs = append(outputs, change) outputs = append(outputs, change)
minTxFee := types.Conf(cfg, "config.wallet").GInt("minFee") if !VerifyCommitValues(inputs, outputs) {
if !VerifyCommitValues(inputs, outputs, minTxFee) {
return nil, nil, errors.Wrap(mixTy.ErrSpendInOutValueNotMatch, "verifyValue") return nil, nil, errors.Wrap(mixTy.ErrSpendInOutValueNotMatch, "verifyValue")
} }
...@@ -143,7 +140,7 @@ func MixTransferInfoVerify(cfg *types.Chain33Config, db dbm.KV, transfer *mixTy. ...@@ -143,7 +140,7 @@ func MixTransferInfoVerify(cfg *types.Chain33Config, db dbm.KV, transfer *mixTy.
3. add nullifier to pool 3. add nullifier to pool
*/ */
func (a *action) Transfer(transfer *mixTy.MixTransferAction) (*types.Receipt, error) { func (a *action) Transfer(transfer *mixTy.MixTransferAction) (*types.Receipt, error) {
inputs, outputs, err := MixTransferInfoVerify(a.api.GetConfig(), a.db, transfer) inputs, outputs, err := MixTransferInfoVerify(a.db, transfer)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "Transfer.MixTransferInfoVerify") return nil, errors.Wrap(err, "Transfer.MixTransferInfoVerify")
} }
......
...@@ -51,21 +51,21 @@ func TestVerifyCommitValuesBasePoint(t *testing.T) { ...@@ -51,21 +51,21 @@ func TestVerifyCommitValuesBasePoint(t *testing.T) {
t.Log("p3.x", p3.X.String()) t.Log("p3.x", p3.X.String())
t.Log("p3.y", p3.Y.String()) t.Log("p3.y", p3.Y.String())
input1 := &mixTy.TransferInputPublicInput{ input1 := &mixTy.TransferInputPublicInput{
AmountX: p1.X.String(), ShieldAmountX: p1.X.String(),
AmountY: p1.Y.String(), ShieldAmountY: p1.Y.String(),
} }
var inputs []*mixTy.TransferInputPublicInput var inputs []*mixTy.TransferInputPublicInput
inputs = append(inputs, input1) inputs = append(inputs, input1)
output1 := &mixTy.TransferOutputPublicInput{ output1 := &mixTy.TransferOutputPublicInput{
AmountX: p2.X.String(), ShieldAmountX: p2.X.String(),
AmountY: p2.Y.String(), ShieldAmountY: p2.Y.String(),
} }
output2 := &mixTy.TransferOutputPublicInput{ output2 := &mixTy.TransferOutputPublicInput{
AmountX: p3.X.String(), ShieldAmountX: p3.X.String(),
AmountY: p3.Y.String(), ShieldAmountY: p3.Y.String(),
} }
var outputs []*mixTy.TransferOutputPublicInput var outputs []*mixTy.TransferOutputPublicInput
...@@ -107,21 +107,21 @@ func TestVerifyCommitValuesBaseAddHPoint(t *testing.T) { ...@@ -107,21 +107,21 @@ func TestVerifyCommitValuesBaseAddHPoint(t *testing.T) {
p3.Add(&p3, &r3) p3.Add(&p3, &r3)
input1 := &mixTy.TransferInputPublicInput{ input1 := &mixTy.TransferInputPublicInput{
AmountX: p1.X.String(), ShieldAmountX: p1.X.String(),
AmountY: p1.Y.String(), ShieldAmountY: p1.Y.String(),
} }
var inputs []*mixTy.TransferInputPublicInput var inputs []*mixTy.TransferInputPublicInput
inputs = append(inputs, input1) inputs = append(inputs, input1)
output1 := &mixTy.TransferOutputPublicInput{ output1 := &mixTy.TransferOutputPublicInput{
AmountX: p2.X.String(), ShieldAmountX: p2.X.String(),
AmountY: p2.Y.String(), ShieldAmountY: p2.Y.String(),
} }
output2 := &mixTy.TransferOutputPublicInput{ output2 := &mixTy.TransferOutputPublicInput{
AmountX: p3.X.String(), ShieldAmountX: p3.X.String(),
AmountY: p3.Y.String(), ShieldAmountY: p3.Y.String(),
} }
var outputs []*mixTy.TransferOutputPublicInput var outputs []*mixTy.TransferOutputPublicInput
......
...@@ -93,7 +93,7 @@ func (a *action) Withdraw(withdraw *mixTy.MixWithdrawAction) (*types.Receipt, er ...@@ -93,7 +93,7 @@ func (a *action) Withdraw(withdraw *mixTy.MixWithdrawAction) (*types.Receipt, er
} }
if sumValue != withdraw.Amount { if sumValue != withdraw.Amount {
return nil, mixTy.ErrInputParaNotMatch return nil, errors.Wrapf(mixTy.ErrInputParaNotMatch, "amount:input=%d,proof sum=%d", withdraw.Amount, sumValue)
} }
//withdraw value //withdraw value
......
...@@ -151,7 +151,6 @@ message TransferOutputPublicInput { ...@@ -151,7 +151,6 @@ message TransferOutputPublicInput {
string shieldAmountX = 2; string shieldAmountX = 2;
string shieldAmountY = 3; string shieldAmountY = 3;
DHSecretGroup dhSecrets = 4;
} }
message AuthorizePublicInput { message AuthorizePublicInput {
......
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
package types package types
import "github.com/33cn/chain33/common/log/log15" import (
"github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/types"
)
var tlog = log15.New("module", MixX) var tlog = log15.New("module", MixX)
...@@ -45,3 +48,6 @@ const ( ...@@ -45,3 +48,6 @@ const (
PointHX = "10190477835300927557649934238820360529458681672073866116232821892325659279502" PointHX = "10190477835300927557649934238820360529458681672073866116232821892325659279502"
PointHY = "7969140283216448215269095418467361784159407896899334866715345504515077887397" PointHY = "7969140283216448215269095418467361784159407896899334866715345504515077887397"
) )
//mix transfer tx fee
const Privacy2PrivacyTxFee = types.Coin
This diff is collapsed.
...@@ -118,7 +118,7 @@ func (p *mixPolicy) processTransfer(transfer *mixTy.MixTransferAction, heightInd ...@@ -118,7 +118,7 @@ func (p *mixPolicy) processTransfer(transfer *mixTy.MixTransferAction, heightInd
return return
} }
outInput := data.(*mixTy.TransferOutputPublicInput) outInput := data.(*mixTy.TransferOutputPublicInput)
p.processSecretGroup(outInput.NoteHash, outInput.DhSecrets, heightIndex, table) p.processSecretGroup(outInput.NoteHash, transfer.Output.Secrets, heightIndex, table)
//change //change
data, err = mixTy.DecodePubInput(mixTy.VerifyType_TRANSFEROUTPUT, transfer.Change.PublicInput) data, err = mixTy.DecodePubInput(mixTy.VerifyType_TRANSFEROUTPUT, transfer.Change.PublicInput)
...@@ -127,7 +127,7 @@ func (p *mixPolicy) processTransfer(transfer *mixTy.MixTransferAction, heightInd ...@@ -127,7 +127,7 @@ func (p *mixPolicy) processTransfer(transfer *mixTy.MixTransferAction, heightInd
return return
} }
changeInput := data.(*mixTy.TransferOutputPublicInput) changeInput := data.(*mixTy.TransferOutputPublicInput)
p.processSecretGroup(changeInput.NoteHash, changeInput.DhSecrets, heightIndex, table) p.processSecretGroup(changeInput.NoteHash, transfer.Change.Secrets, heightIndex, table)
} }
...@@ -327,18 +327,17 @@ func (p *mixPolicy) decodeSecret(noteHash string, secretData string, privacyKeys ...@@ -327,18 +327,17 @@ func (p *mixPolicy) decodeSecret(noteHash string, secretData string, privacyKeys
} }
decryptData, err := decryptData(key.Privacy.ShareSecretKey.PrivKey, tempPubKey, cryptData) decryptData, err := decryptData(key.Privacy.ShareSecretKey.PrivKey, tempPubKey, cryptData)
if err != nil { if err != nil {
bizlog.Info("processSecret.decryptData", "decrypt for notehash", noteHash, "secret", secretData, "addr", key.Addr, "err", err) bizlog.Debug("processSecret.decryptData fail", "decrypt for notehash", noteHash, "secret", secretData, "addr", key.Addr, "err", err)
continue continue
} }
var rawData mixTy.SecretData var rawData mixTy.SecretData
err = types.Decode(decryptData, &rawData) err = types.Decode(decryptData, &rawData)
if err != nil { if err != nil {
bizlog.Info("processSecret.decode rawData", "addr", key.Addr, "err", err) bizlog.Debug("processSecret.decode rawData fail", "addr", key.Addr, "err", err)
continue continue
} }
bizlog.Info("processSecret.decode rawData OK", "notehash", noteHash, "addr", key.Addr) bizlog.Info("processSecret.decode rawData OK", "notehash", noteHash, "addr", key.Addr)
if rawData.ReceiverPubKey == key.Privacy.PaymentKey.ReceiverPubKey || if rawData.ReceiverPubKey == key.Privacy.PaymentKey.ReceiverPubKey ||
rawData.ReturnPubKey == key.Privacy.PaymentKey.ReceiverPubKey || rawData.ReturnPubKey == key.Privacy.PaymentKey.ReceiverPubKey ||
rawData.AuthorizePubKey == key.Privacy.PaymentKey.ReceiverPubKey { rawData.AuthorizePubKey == key.Privacy.PaymentKey.ReceiverPubKey {
......
...@@ -176,6 +176,7 @@ func (policy *mixPolicy) SignTransaction(key crypto.PrivKey, req *types.ReqSignR ...@@ -176,6 +176,7 @@ func (policy *mixPolicy) SignTransaction(key crypto.PrivKey, req *types.ReqSignR
func (policy *mixPolicy) signatureTx(tx *types.Transaction, transfer *mixTy.MixTransferAction) error { func (policy *mixPolicy) signatureTx(tx *types.Transaction, transfer *mixTy.MixTransferAction) error {
cfg := policy.getWalletOperate().GetAPI().GetConfig() cfg := policy.getWalletOperate().GetAPI().GetConfig()
mixSignData := types.Encode(transfer) mixSignData := types.Encode(transfer)
tx.Fee = mixTy.Privacy2PrivacyTxFee
tx.Signature = &types.Signature{ tx.Signature = &types.Signature{
Ty: MixSignID, Ty: MixSignID,
Signature: common.BytesToHash(mixSignData).Bytes(), Signature: common.BytesToHash(mixSignData).Bytes(),
......
...@@ -214,7 +214,7 @@ func (policy *mixPolicy) withdrawProof(req *mixTy.WithdrawProofReq) (*mixTy.With ...@@ -214,7 +214,7 @@ func (policy *mixPolicy) withdrawProof(req *mixTy.WithdrawProofReq) (*mixTy.With
if note.IsReturner { if note.IsReturner {
resp.SpendFlag = 0 resp.SpendFlag = 0
} }
if len(resp.AuthorizeSpendHash) > 0 { if len(resp.AuthorizeSpendHash) > LENNULLKEY {
resp.AuthorizeFlag = 1 resp.AuthorizeFlag = 1
} }
...@@ -225,16 +225,11 @@ func (policy *mixPolicy) withdrawProof(req *mixTy.WithdrawProofReq) (*mixTy.With ...@@ -225,16 +225,11 @@ func (policy *mixPolicy) withdrawProof(req *mixTy.WithdrawProofReq) (*mixTy.With
} }
resp.SpendPrivKey = privacyKey.Privacy.PaymentKey.SpendPriKey resp.SpendPrivKey = privacyKey.Privacy.PaymentKey.SpendPriKey
//get tree path //get tree path
path, err := policy.getPathProof(note.NoteHash) treeProof, err := policy.getTreeProof(note.NoteHash)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "get tree proof for noteHash=%s", note.NoteHash) return nil, errors.Wrapf(err, "getTreeProof for hash=%s", note.NoteHash)
}
resp.TreeProof.TreePath = path.ProofSet[1:]
resp.TreeProof.Helpers = path.Helpers
for i := 0; i < len(resp.TreeProof.TreePath); i++ {
resp.TreeProof.ValidPath = append(resp.TreeProof.ValidPath, 1)
} }
resp.TreeProof.TreeRootHash = path.RootHash resp.TreeProof = treeProof
return &resp, nil return &resp, nil
...@@ -274,16 +269,11 @@ func (policy *mixPolicy) authProof(req *mixTy.AuthProofReq) (*mixTy.AuthProofRes ...@@ -274,16 +269,11 @@ func (policy *mixPolicy) authProof(req *mixTy.AuthProofReq) (*mixTy.AuthProofRes
} }
//get tree path //get tree path
path, err := policy.getPathProof(note.NoteHash) treeProof, err := policy.getTreeProof(note.NoteHash)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "get tree proof for noteHash=%s", note.NoteHash) return nil, errors.Wrapf(err, "getTreeProof for hash=%s", note.NoteHash)
}
resp.TreeProof.TreePath = path.ProofSet[1:]
resp.TreeProof.Helpers = path.Helpers
for i := 0; i < len(resp.TreeProof.TreePath); i++ {
resp.TreeProof.ValidPath = append(resp.TreeProof.ValidPath, 1)
} }
resp.TreeProof.TreeRootHash = path.RootHash resp.TreeProof = treeProof
return &resp, nil return &resp, nil
...@@ -412,8 +402,6 @@ func (policy *mixPolicy) transferProof(req *mixTy.TransferProofReq) (*mixTy.Tran ...@@ -412,8 +402,6 @@ func (policy *mixPolicy) transferProof(req *mixTy.TransferProofReq) (*mixTy.Tran
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "input part parseUint=%s", inputPart.Proof.Amount) return nil, errors.Wrapf(err, "input part parseUint=%s", inputPart.Proof.Amount)
} }
//还要扣除手续费
minTxFee := uint64(policy.walletOperate.GetConfig().MinFee)
//output toAddr part //output toAddr part
reqTransfer := &mixTy.DepositProofReq{ reqTransfer := &mixTy.DepositProofReq{
...@@ -428,11 +416,12 @@ func (policy *mixPolicy) transferProof(req *mixTy.TransferProofReq) (*mixTy.Tran ...@@ -428,11 +416,12 @@ func (policy *mixPolicy) transferProof(req *mixTy.TransferProofReq) (*mixTy.Tran
} }
bizlog.Info("transferProof deposit to receiver succ", "notehash", req.NoteHash) bizlog.Info("transferProof deposit to receiver succ", "notehash", req.NoteHash)
//还要扣除手续费
//output 找零 part,如果找零为0也需要设置,否则只有一个输入一个输出,H部分的随机数要相等,就能推测出转账值来 //output 找零 part,如果找零为0也需要设置,否则只有一个输入一个输出,H部分的随机数要相等,就能推测出转账值来
//在transfer output 部分特殊处理,如果amount是0的值则不加进tree //在transfer output 部分特殊处理,如果amount是0的值则不加进tree
reqChange := &mixTy.DepositProofReq{ reqChange := &mixTy.DepositProofReq{
ReceiverAddr: note.Account, ReceiverAddr: note.Account,
Amount: noteAmount - req.Amount - minTxFee, Amount: noteAmount - req.Amount - uint64(mixTy.Privacy2PrivacyTxFee),
} }
depositChange, err := policy.depositProof(reqChange) depositChange, err := policy.depositProof(reqChange)
if err != nil { if err != nil {
...@@ -440,7 +429,7 @@ func (policy *mixPolicy) transferProof(req *mixTy.TransferProofReq) (*mixTy.Tran ...@@ -440,7 +429,7 @@ func (policy *mixPolicy) transferProof(req *mixTy.TransferProofReq) (*mixTy.Tran
} }
bizlog.Info("transferProof deposit to change succ", "notehash", req.NoteHash) bizlog.Info("transferProof deposit to change succ", "notehash", req.NoteHash)
commitValue, err := getCommitValue(noteAmount, req.Amount, minTxFee) commitValue, err := getCommitValue(noteAmount, req.Amount, uint64(mixTy.Privacy2PrivacyTxFee))
if err != nil { if err != nil {
return nil, err return nil, err
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment