Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
sgxwallet
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
董子豪
sgxwallet
Commits
86a5cac3
Unverified
Commit
86a5cac3
authored
Jan 29, 2020
by
svetaro
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'bug/SKALE-2074-SIGILL-in-SGX-server' into enhancement/SKALE-2003-Create-backup-key
parents
dd9fc46e
aa427319
Changes
9
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
351 additions
and
178 deletions
+351
-178
DKGCrypto.cpp
DKGCrypto.cpp
+62
-21
Makefile.am
Makefile.am
+3
-2
SGXWalletServer.cpp
SGXWalletServer.cpp
+1
-1
ServerDataChecker.cpp
ServerDataChecker.cpp
+3
-0
enclave_common.h
secure_enclave/enclave_common.h
+1
-1
secure_enclave.c
secure_enclave/secure_enclave.c
+146
-31
secure_enclave.edl
secure_enclave/secure_enclave.edl
+20
-0
stubclient.h
stubclient.h
+1
-1
testw.cpp
testw.cpp
+114
-121
No files found.
DKGCrypto.cpp
View file @
86a5cac3
...
@@ -78,6 +78,7 @@ string ConvertToString(T field_elem, int base = 10) {
...
@@ -78,6 +78,7 @@ string ConvertToString(T field_elem, int base = 10) {
string
gen_dkg_poly
(
int
_t
){
string
gen_dkg_poly
(
int
_t
){
vector
<
char
>
errMsg
(
1024
,
0
);
vector
<
char
>
errMsg
(
1024
,
0
);
int
err_status
=
0
;
int
err_status
=
0
;
vector
<
uint8_t
>
encrypted_dkg_secret
(
DKG_MAX_SEALED_LEN
,
0
);
vector
<
uint8_t
>
encrypted_dkg_secret
(
DKG_MAX_SEALED_LEN
,
0
);
uint32_t
enc_len
=
0
;
uint32_t
enc_len
=
0
;
...
@@ -95,9 +96,17 @@ string gen_dkg_poly( int _t){
...
@@ -95,9 +96,17 @@ string gen_dkg_poly( int _t){
spdlog
::
info
(
"in DKGCrypto encr len is {}"
,
enc_len
);
spdlog
::
info
(
"in DKGCrypto encr len is {}"
,
enc_len
);
}
}
vector
<
char
>
hexEncrPoly
(
DKG_MAX_SEALED_LEN
*
2
+
1
,
0
);
//(4*BUF_LEN, 1);
uint64_t
length
=
DKG_MAX_SEALED_LEN
;
if
(
is_aes
){
length
=
enc_len
;
}
//vector<char> hexEncrPoly(DKG_MAX_SEALED_LEN * 2 + 1, 0);//(4*BUF_LEN, 1);
carray2Hex
(
encrypted_dkg_secret
.
data
(),
DKG_MAX_SEALED_LEN
,
hexEncrPoly
.
data
());
vector
<
char
>
hexEncrPoly
(
2
*
length
,
0
);
//carray2Hex(encrypted_dkg_secret.data(), DKG_MAX_SEALED_LEN, hexEncrPoly.data());
carray2Hex
(
encrypted_dkg_secret
.
data
(),
length
,
hexEncrPoly
.
data
());
string
result
(
hexEncrPoly
.
data
());
string
result
(
hexEncrPoly
.
data
());
return
result
;
return
result
;
...
@@ -106,6 +115,7 @@ string gen_dkg_poly( int _t){
...
@@ -106,6 +115,7 @@ string gen_dkg_poly( int _t){
vector
<
vector
<
string
>>
get_verif_vect
(
const
char
*
encryptedPolyHex
,
int
t
,
int
n
){
vector
<
vector
<
string
>>
get_verif_vect
(
const
char
*
encryptedPolyHex
,
int
t
,
int
n
){
char
*
errMsg1
=
(
char
*
)
calloc
(
1024
,
1
);
char
*
errMsg1
=
(
char
*
)
calloc
(
1024
,
1
);
//char errMsg1[BUF_LEN];
int
err_status
=
0
;
int
err_status
=
0
;
if
(
DEBUG_PRINT
)
{
if
(
DEBUG_PRINT
)
{
...
@@ -114,23 +124,33 @@ vector <vector<string>> get_verif_vect(const char* encryptedPolyHex, int t, int
...
@@ -114,23 +124,33 @@ vector <vector<string>> get_verif_vect(const char* encryptedPolyHex, int t, int
}
}
char
*
public_shares
=
(
char
*
)
calloc
(
10000
,
1
);
char
*
public_shares
=
(
char
*
)
calloc
(
10000
,
1
);
// char public_shares[10000];
uint64_t
enc_len
=
0
;
uint64_t
enc_len
=
0
;
uint8_t
*
encr_dkg_poly
=
(
uint8_t
*
)
calloc
(
DKG_MAX_SEALED_LEN
,
1
);
uint8_t
*
encr_dkg_poly
=
(
uint8_t
*
)
calloc
(
DKG_MAX_SEALED_LEN
*
2
,
1
);
//uint8_t encr_dkg_poly[DKG_MAX_SEALED_LEN];
if
(
!
hex2carray2
(
encryptedPolyHex
,
&
enc_len
,
encr_dkg_poly
,
6100
)){
if
(
!
hex2carray2
(
encryptedPolyHex
,
&
enc_len
,
encr_dkg_poly
,
6100
)){
throw
RPCException
(
INVALID_HEX
,
"Invalid encryptedPolyHex"
);
throw
RPCException
(
INVALID_HEX
,
"Invalid encryptedPolyHex"
);
}
}
if
(
DEBUG_PRINT
)
{
if
(
DEBUG_PRINT
)
{
cerr
<<
"hex_encr_poly is "
<<
encryptedPolyHex
<<
std
::
endl
;
spdlog
::
info
(
"hex_encr_poly length is {}"
,
strlen
(
encryptedPolyHex
));
spdlog
::
info
(
"enc len {}"
,
enc_len
);
spdlog
::
info
(
"enc len {}"
,
enc_len
);
/*
cerr << "encr raw poly: " << endl;
//
cerr << "encr raw poly: " << endl;
for ( int i = 0 ; i < 3050; i++)
//
for ( int i = 0 ; i < 3050; i++)
printf(" %d ", encr_dkg_poly[i] );*/
// printf(" %d ", encr_dkg_poly[i] );
}
}
uint32_t
len
=
0
;
uint32_t
len
;
status
=
get_public_shares
(
eid
,
&
err_status
,
errMsg1
,
encr_dkg_poly
,
len
,
public_shares
,
t
,
n
);
if
(
!
is_aes
)
status
=
get_public_shares
(
eid
,
&
err_status
,
errMsg1
,
encr_dkg_poly
,
len
,
public_shares
,
t
,
n
);
else
{
status
=
get_public_shares_aes
(
eid
,
&
err_status
,
errMsg1
,
encr_dkg_poly
,
enc_len
,
public_shares
,
t
,
n
);
}
if
(
err_status
!=
0
){
if
(
err_status
!=
0
){
throw
RPCException
(
-
666
,
errMsg1
);
throw
RPCException
(
-
666
,
errMsg1
);
}
}
...
@@ -161,12 +181,14 @@ vector <vector<string>> get_verif_vect(const char* encryptedPolyHex, int t, int
...
@@ -161,12 +181,14 @@ vector <vector<string>> get_verif_vect(const char* encryptedPolyHex, int t, int
}
}
string
get_secret_shares
(
const
string
&
polyName
,
const
char
*
encryptedPolyHex
,
const
vector
<
string
>&
publicKeys
,
int
t
,
int
n
){
string
get_secret_shares
(
const
string
&
polyName
,
const
char
*
encryptedPolyHex
,
const
vector
<
string
>&
publicKeys
,
int
t
,
int
n
){
char
*
errMsg1
=
(
char
*
)
calloc
(
1024
,
1
);
//char* errMsg1 = (char*) calloc(1024,1);
char
errMsg1
[
BUF_LEN
];
int
err_status
=
0
;
int
err_status
=
0
;
uint64_t
enc_len
=
0
;
uint64_t
enc_len
=
0
;
uint8_t
*
encr_dkg_poly
=
(
uint8_t
*
)
calloc
(
DKG_MAX_SEALED_LEN
,
1
);
// uint8_t* encr_dkg_poly = (uint8_t*) calloc(DKG_MAX_SEALED_LEN, 1);
uint8_t
encr_dkg_poly
[
DKG_MAX_SEALED_LEN
];
if
(
!
hex2carray2
(
encryptedPolyHex
,
&
enc_len
,
encr_dkg_poly
,
6100
)){
if
(
!
hex2carray2
(
encryptedPolyHex
,
&
enc_len
,
encr_dkg_poly
,
6100
)){
throw
RPCException
(
INVALID_HEX
,
"Invalid encryptedPolyHex"
);
throw
RPCException
(
INVALID_HEX
,
"Invalid encryptedPolyHex"
);
}
}
...
@@ -181,7 +203,8 @@ string get_secret_shares(const string& polyName, const char* encryptedPolyHex, c
...
@@ -181,7 +203,8 @@ string get_secret_shares(const string& polyName, const char* encryptedPolyHex, c
}
}
string
result
;
string
result
;
char
*
hexEncrKey
=
(
char
*
)
calloc
(
2
*
BUF_LEN
,
1
);
//char *hexEncrKey = (char *) calloc(2 * BUF_LEN, 1);
char
hexEncrKey
[
2
*
BUF_LEN
];
for
(
int
i
=
0
;
i
<
n
;
i
++
){
for
(
int
i
=
0
;
i
<
n
;
i
++
){
uint8_t
encrypted_skey
[
BUF_LEN
];
uint8_t
encrypted_skey
[
BUF_LEN
];
...
@@ -196,11 +219,22 @@ string get_secret_shares(const string& polyName, const char* encryptedPolyHex, c
...
@@ -196,11 +219,22 @@ string get_secret_shares(const string& polyName, const char* encryptedPolyHex, c
char
pubKeyB
[
129
];
char
pubKeyB
[
129
];
strncpy
(
pubKeyB
,
pub_keyB
.
c_str
(),
128
);
strncpy
(
pubKeyB
,
pub_keyB
.
c_str
(),
128
);
pubKeyB
[
128
]
=
0
;
pubKeyB
[
128
]
=
0
;
get_encr_sshare
(
eid
,
&
err_status
,
errMsg1
,
encrypted_skey
,
&
dec_len
,
if
(
DEBUG_PRINT
)
{
spdlog
::
info
(
"pubKeyB is {}"
,
pub_keyB
);
}
if
(
!
is_aes
)
get_encr_sshare
(
eid
,
&
err_status
,
errMsg1
,
encrypted_skey
,
&
dec_len
,
cur_share
,
s_shareG2
,
pubKeyB
,
t
,
n
,
i
+
1
);
cur_share
,
s_shareG2
,
pubKeyB
,
t
,
n
,
i
+
1
);
else
get_encr_sshare_aes
(
eid
,
&
err_status
,
errMsg1
,
encrypted_skey
,
&
dec_len
,
cur_share
,
s_shareG2
,
pubKeyB
,
t
,
n
,
i
+
1
);
if
(
err_status
!=
0
){
if
(
err_status
!=
0
){
throw
RPCException
(
-
666
,
errMsg1
);
throw
RPCException
(
-
666
,
errMsg1
);
}
}
if
(
DEBUG_PRINT
)
{
spdlog
::
info
(
"cur_share is {}"
,
cur_share
);
}
result
+=
cur_share
;
result
+=
cur_share
;
...
@@ -232,15 +266,16 @@ string get_secret_shares(const string& polyName, const char* encryptedPolyHex, c
...
@@ -232,15 +266,16 @@ string get_secret_shares(const string& polyName, const char* encryptedPolyHex, c
}
}
//result += '\0';
//result += '\0';
free
(
encr_dkg_poly
);
//
free(encr_dkg_poly);
free
(
errMsg1
);
//
free(errMsg1);
free
(
hexEncrKey
);
//
free(hexEncrKey);
return
result
;
return
result
;
}
}
bool
VerifyShares
(
const
char
*
publicShares
,
const
char
*
encr_sshare
,
const
char
*
encryptedKeyHex
,
int
t
,
int
n
,
int
ind
){
bool
VerifyShares
(
const
char
*
publicShares
,
const
char
*
encr_sshare
,
const
char
*
encryptedKeyHex
,
int
t
,
int
n
,
int
ind
){
char
*
errMsg1
=
(
char
*
)
calloc
(
1024
,
1
);
//char* errMsg1 = (char*) calloc(1024,1);
char
errMsg1
[
BUF_LEN
];
int
err_status
=
0
;
int
err_status
=
0
;
uint64_t
dec_key_len
;
uint64_t
dec_key_len
;
...
@@ -263,6 +298,7 @@ bool VerifyShares(const char* publicShares, const char* encr_sshare, const char
...
@@ -263,6 +298,7 @@ bool VerifyShares(const char* publicShares, const char* encr_sshare, const char
dkg_verification
(
eid
,
&
err_status
,
errMsg1
,
pshares
,
encr_sshare
,
encr_key
,
dec_key_len
,
t
,
ind
,
&
result
);
dkg_verification
(
eid
,
&
err_status
,
errMsg1
,
pshares
,
encr_sshare
,
encr_key
,
dec_key_len
,
t
,
ind
,
&
result
);
if
(
result
==
2
){
if
(
result
==
2
){
throw
RPCException
(
INVALID_HEX
,
"Invalid public shares"
);
throw
RPCException
(
INVALID_HEX
,
"Invalid public shares"
);
}
}
...
@@ -272,7 +308,7 @@ bool VerifyShares(const char* publicShares, const char* encr_sshare, const char
...
@@ -272,7 +308,7 @@ bool VerifyShares(const char* publicShares, const char* encr_sshare, const char
spdlog
::
info
(
"result is: {}"
,
result
);
spdlog
::
info
(
"result is: {}"
,
result
);
}
}
free
(
errMsg1
);
//
free(errMsg1);
return
result
;
return
result
;
}
}
...
@@ -281,7 +317,8 @@ bool CreateBLSShare( const string& blsKeyName, const char * s_shares, const char
...
@@ -281,7 +317,8 @@ bool CreateBLSShare( const string& blsKeyName, const char * s_shares, const char
if
(
DEBUG_PRINT
)
{
if
(
DEBUG_PRINT
)
{
spdlog
::
info
(
"ENTER CreateBLSShare"
);
spdlog
::
info
(
"ENTER CreateBLSShare"
);
}
}
char
*
errMsg1
=
(
char
*
)
calloc
(
1024
,
1
);
// char* errMsg1 = (char*) calloc(1024,1);
char
errMsg1
[
BUF_LEN
];
int
err_status
=
0
;
int
err_status
=
0
;
uint64_t
dec_key_len
;
uint64_t
dec_key_len
;
...
@@ -301,7 +338,9 @@ bool CreateBLSShare( const string& blsKeyName, const char * s_shares, const char
...
@@ -301,7 +338,9 @@ bool CreateBLSShare( const string& blsKeyName, const char * s_shares, const char
throw
RPCException
(
ERROR_IN_ENCLAVE
,
"Create BLS private key failed in enclave"
);
throw
RPCException
(
ERROR_IN_ENCLAVE
,
"Create BLS private key failed in enclave"
);
}
}
else
{
else
{
char
*
hexBLSKey
=
(
char
*
)
calloc
(
2
*
BUF_LEN
,
1
);
//char *hexBLSKey = (char *) calloc(2 * BUF_LEN, 1);
char
hexBLSKey
[
2
*
BUF_LEN
];
//cerr << "BEFORE carray2Hex" << endl;
//cerr << "BEFORE carray2Hex" << endl;
//cerr << "enc_bls_len " << enc_bls_len << endl;
//cerr << "enc_bls_len " << enc_bls_len << endl;
carray2Hex
(
encr_bls_key
,
enc_bls_len
,
hexBLSKey
);
carray2Hex
(
encr_bls_key
,
enc_bls_len
,
hexBLSKey
);
...
@@ -311,14 +350,16 @@ bool CreateBLSShare( const string& blsKeyName, const char * s_shares, const char
...
@@ -311,14 +350,16 @@ bool CreateBLSShare( const string& blsKeyName, const char * s_shares, const char
spdlog
::
info
(
"hexBLSKey length is {}"
,
char_traits
<
char
>::
length
(
hexBLSKey
));
spdlog
::
info
(
"hexBLSKey length is {}"
,
char_traits
<
char
>::
length
(
hexBLSKey
));
spdlog
::
info
(
"bls key {}"
,
blsKeyName
,
" is "
,
hexBLSKey
);
spdlog
::
info
(
"bls key {}"
,
blsKeyName
,
" is "
,
hexBLSKey
);
}
}
free
(
hexBLSKey
);
//
free(hexBLSKey);
return
true
;
return
true
;
}
}
}
}
vector
<
string
>
GetBLSPubKey
(
const
char
*
encryptedKeyHex
){
vector
<
string
>
GetBLSPubKey
(
const
char
*
encryptedKeyHex
){
char
*
errMsg1
=
(
char
*
)
calloc
(
1024
,
1
);
//char* errMsg1 = (char*) calloc(1024,1);
char
errMsg1
[
BUF_LEN
];
int
err_status
=
0
;
int
err_status
=
0
;
uint64_t
dec_key_len
;
uint64_t
dec_key_len
;
...
...
Makefile.am
View file @
86a5cac3
...
@@ -43,8 +43,9 @@ secure_enclave.edl: secure_enclave/secure_enclave.edl
...
@@ -43,8 +43,9 @@ secure_enclave.edl: secure_enclave/secure_enclave.edl
## Additional automake variables
## Additional automake variables
##
##
#AM_CPPFLAGS += -g -Og
#AM_CPPFLAGS += -g -Og
#AM_CFLAGS = -g -Og
#AM_CXXFLAGS = ${AM_CPPFLAGS}
AM_CFLAGS
=
-g
-Og
-rdynamic
-Wl
,--no-as-needed
-lSegFault
AM_CXXFLAGS
=
${
AM_CPPFLAGS
}
-rdynamic
-Wl
,--no-as-needed
-lSegFault
AM_CPPFLAGS
+=
-Wall
-DSKALE_SGX
=
1
-DBINARY_OUTPUT
=
1
-Ileveldb
/include
-IlibBLS
/bls
-IlibBLS
/libff
-IlibBLS
-fno-builtin-memset
$(GMP_CPPFLAGS)
-I
.
-I
./libBLS/deps/deps_inst/x86_or_x64/include
AM_CPPFLAGS
+=
-Wall
-DSKALE_SGX
=
1
-DBINARY_OUTPUT
=
1
-Ileveldb
/include
-IlibBLS
/bls
-IlibBLS
/libff
-IlibBLS
-fno-builtin-memset
$(GMP_CPPFLAGS)
-I
.
-I
./libBLS/deps/deps_inst/x86_or_x64/include
...
...
SGXWalletServer.cpp
View file @
86a5cac3
...
@@ -114,7 +114,7 @@ int init_https_server(bool check_certs) {
...
@@ -114,7 +114,7 @@ int init_https_server(bool check_certs) {
}
}
}
}
hs
=
new
HttpServer
(
BASE_PORT
,
certPath
,
keyPath
,
rootCAPath
,
check_certs
,
10
);
hs
=
new
HttpServer
(
BASE_PORT
,
certPath
,
keyPath
,
rootCAPath
,
check_certs
,
64
);
s
=
new
SGXWalletServer
(
*
hs
,
s
=
new
SGXWalletServer
(
*
hs
,
JSONRPC_SERVER_V2
);
// hybrid server (json-rpc 1.0 & 2.0)
JSONRPC_SERVER_V2
);
// hybrid server (json-rpc 1.0 & 2.0)
...
...
ServerDataChecker.cpp
View file @
86a5cac3
...
@@ -72,6 +72,8 @@ bool checkECDSAKeyName(const string& keyName) {
...
@@ -72,6 +72,8 @@ bool checkECDSAKeyName(const string& keyName) {
bool
checkHex
(
const
string
&
hex
,
const
uint32_t
sizeInBytes
){
bool
checkHex
(
const
string
&
hex
,
const
uint32_t
sizeInBytes
){
if
(
hex
.
length
()
>
sizeInBytes
*
2
||
hex
.
length
()
==
0
){
if
(
hex
.
length
()
>
sizeInBytes
*
2
||
hex
.
length
()
==
0
){
spdlog
::
error
(
"public key is too long or zero - "
,
hex
.
length
());
std
::
cerr
<<
"public key length is "
<<
hex
.
length
()
<<
std
::
endl
;
return
false
;
return
false
;
}
}
...
@@ -79,6 +81,7 @@ bool checkHex(const string& hex, const uint32_t sizeInBytes){
...
@@ -79,6 +81,7 @@ bool checkHex(const string& hex, const uint32_t sizeInBytes){
mpz_init
(
num
);
mpz_init
(
num
);
if
(
mpz_set_str
(
num
,
hex
.
c_str
(),
16
)
==
-
1
){
if
(
mpz_set_str
(
num
,
hex
.
c_str
(),
16
)
==
-
1
){
spdlog
::
error
(
"public key is not hex {}"
,
hex
);
mpz_clear
(
num
);
mpz_clear
(
num
);
return
false
;
return
false
;
}
}
...
...
secure_enclave/enclave_common.h
View file @
86a5cac3
...
@@ -18,7 +18,7 @@
...
@@ -18,7 +18,7 @@
#define ADD_ENTROPY_SIZE 32
#define ADD_ENTROPY_SIZE 32
#define DKG_BUFER_LENGTH 2490//3060
#define DKG_BUFER_LENGTH 2490//3060
#define DKG_MAX_SEALED_LEN 3
05
0
#define DKG_MAX_SEALED_LEN 3
10
0
#define SECRET_SHARE_NUM_BYTES 96
#define SECRET_SHARE_NUM_BYTES 96
...
...
secure_enclave/secure_enclave.c
View file @
86a5cac3
This diff is collapsed.
Click to expand it.
secure_enclave/secure_enclave.edl
View file @
86a5cac3
...
@@ -233,6 +233,26 @@ enclave {
...
@@ -233,6 +233,26 @@ enclave {
[in, count = 3050] uint8_t* encrypted_poly,
[in, count = 3050] uint8_t* encrypted_poly,
[user_check] uint64_t* enc_len);
[user_check] uint64_t* enc_len);
public void get_encr_sshare_aes(
[user_check]int *err_status,
[out, count = 1024] char *err_string,
[out, count = 1024] uint8_t *encrypted_skey,
[user_check] uint32_t* dec_len,
[out, count = 193] char* result_str,
[out, count = 320] char* s_shareG2,
[in, count = 129] char* pub_keyB,
uint8_t _t,
uint8_t _n,
uint8_t ind);
public void get_public_shares_aes (
[user_check] int *err_status,
[out, count = 1024] char* err_string,
[in, count = 3050] uint8_t* encrypted_dkg_secret,
uint32_t enc_len,
[out, count = 10000] char* public_shares,
unsigned _t,
unsigned _n);
...
...
stubclient.h
View file @
86a5cac3
...
@@ -165,7 +165,7 @@ class StubClient : public jsonrpc::Client
...
@@ -165,7 +165,7 @@ class StubClient : public jsonrpc::Client
p
[
"secretShare"
]
=
SecretShare
;
p
[
"secretShare"
]
=
SecretShare
;
p
[
"n"
]
=
n
;
p
[
"n"
]
=
n
;
p
[
"t"
]
=
t
;
p
[
"t"
]
=
t
;
Json
::
Value
result
=
this
->
CallMethod
(
"reateBLSPrivateKey"
,
p
);
Json
::
Value
result
=
this
->
CallMethod
(
"
c
reateBLSPrivateKey"
,
p
);
if
(
result
.
isObject
())
if
(
result
.
isObject
())
return
result
;
return
result
;
else
else
...
...
testw.cpp
View file @
86a5cac3
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment