diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 8475ec7d5..f75fcb50e 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -3,9 +3,9 @@ rust_packages := rust librustzcash proton_packages := proton zcash_packages := libgmp libsodium ifeq ($(host_os),linux) - packages := boost openssl libevent zeromq $(zcash_packages) #googletest googlemock + packages := boost openssl libevent zeromq $(zcash_packages) googletest # googlemock else - packages := boost openssl libevent zeromq $(zcash_packages) libcurl # googletest googlemock libcurl + packages := boost openssl libevent zeromq $(zcash_packages) libcurl googletest # googlemock endif native_packages := native_ccache diff --git a/qa/cryptoconditions/README.md b/qa/cryptoconditions/README.md deleted file mode 100644 index 2bf479ecd..000000000 --- a/qa/cryptoconditions/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Integration tests for Crypto-Conditions - -These tests are for the [Crypto-Conditions](https://github.com/rfcs/crypto-conditions) functionality in Komodod, using [Hoek](https://github.com/libscott/hoek) as a tool to create and sign transactions. - -## How to run the tests - -1. Install hoek: https://github.com/libscott/hoek -1. Allow peer-less block creation: set "fMiningRequiresPeers = false" in src/chainparams.cpp, then build komodod. -1. Start komodod: `src/komodod -ac_name=CCTEST -ac_supply=21000000 -gen -pubkey=0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47 -ac_cc=1` -1. Set environment variable pointing to Komodo conf: `export KOMODO_CONF_PATH=~/.komodo/CCTEST/CCTEST.conf` -1. Run tests: `python test_integration.py` (you can also use a test runner such as `nose`). diff --git a/qa/cryptoconditions/test_integration.py b/qa/cryptoconditions/test_integration.py deleted file mode 100644 index bd72b961d..000000000 --- a/qa/cryptoconditions/test_integration.py +++ /dev/null @@ -1,223 +0,0 @@ -import sys -import time -import json -import logging -import binascii -import struct -from testsupport import * - - -SCRIPT_FALSE = 'Script evaluated without error but finished with a false/empty top stack element' - - -@fanout_input(0) -def test_basic_spend(inp): - spend = {'inputs': [inp], "outputs": [nospend]} - spend_txid = submit(sign(spend)) - assert rpc.getrawtransaction(spend_txid) - - -@fanout_input(1) -def test_fulfillment_wrong_signature(inp): - # Set other pubkey and sign - inp['script']['fulfillment']['publicKey'] = bob_pk - spend = {'inputs': [inp], 'outputs': [nospend]} - signed = sign(spend) - - # Set the correct pubkey, signature is bob's - signed['inputs'][0]['script']['fulfillment']['publicKey'] = alice_pk - - try: - assert not submit(signed), 'should raise an error' - except RPCError as e: - assert SCRIPT_FALSE in str(e), str(e) - - -@fanout_input(2) -def test_fulfillment_wrong_pubkey(inp): - spend = {'inputs': [inp], 'outputs': [nospend]} - signed = sign(spend) - - # Set the wrong pubkey, signature is correct - signed['inputs'][0]['script']['fulfillment']['publicKey'] = bob_pk - - try: - assert not submit(signed), 'should raise an error' - except RPCError as e: - assert SCRIPT_FALSE in str(e), str(e) - - -@fanout_input(3) -def test_invalid_fulfillment_binary(inp): - # Create a valid script with an invalid fulfillment payload - inp['script'] = binascii.hexlify(b"\007invalid").decode('utf-8') - spend = {'inputs': [inp], 'outputs': [nospend]} - - try: - assert not submit(spend), 'should raise an error' - except RPCError as e: - assert 'Crypto-Condition payload is invalid' in str(e), str(e) - - -@fanout_input(4) -def test_invalid_condition(inp): - # Create a valid output script with an invalid cryptocondition binary - outputscript = to_hex(b"\007invalid\xcc") - spend = {'inputs': [inp], 'outputs': [{'amount': 1000, 'script': outputscript}]} - spend_txid = submit(sign(spend)) - - spend1 = { - 'inputs': [{'txid': spend_txid, 'idx': 0, 'script': {'fulfillment': cond_alice}}], - 'outputs': [nospend], - } - - try: - assert not submit(sign(spend1)), 'should raise an error' - except RPCError as e: - assert SCRIPT_FALSE in str(e), str(e) - - -@fanout_input(5) -def test_oversize_fulfillment(inp): - # Create oversize fulfillment script where the total length is <2000 - binscript = b'\x4d%s%s' % (struct.pack('h', 2000), b'a' * 2000) - inp['script'] = to_hex(binscript) - spend = {'inputs': [inp], 'outputs': [nospend]} - - try: - assert not submit(spend), 'should raise an error' - except RPCError as e: - assert 'scriptsig-size' in str(e), str(e) - - -@fanout_input(6) -def test_aux_basic(inp): - aux_cond = { - 'type': 'aux-sha-256', - 'method': 'equals', - 'conditionAux': 'LTE', - 'fulfillmentAux': 'LTE' - } - - # Setup some aux outputs - spend0 = { - 'inputs': [inp], - 'outputs': [ - {'amount': 500, 'script': {'condition': aux_cond}}, - {'amount': 500, 'script': {'condition': aux_cond}} - ] - } - spend0_txid = submit(sign(spend0)) - assert rpc.getrawtransaction(spend0_txid) - - # Test a good fulfillment - spend1 = { - 'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': aux_cond}}], - 'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}] - } - spend1_txid = submit(sign(spend1)) - assert rpc.getrawtransaction(spend1_txid) - - # Test a bad fulfillment - aux_cond['fulfillmentAux'] = 'WYW' - spend2 = { - 'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': aux_cond}}], - 'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}] - } - try: - assert not submit(sign(spend2)), 'should raise an error' - except RPCError as e: - assert SCRIPT_FALSE in str(e), str(e) - - -@fanout_input(7) -def test_aux_complex(inp): - aux_cond = { - 'type': 'aux-sha-256', - 'method': 'inputIsReturn', - 'conditionAux': '', - 'fulfillmentAux': 'AQ' # \1 (tx.vout[1]) - } - - # Setup some aux outputs - spend0 = { - 'inputs': [inp], - 'outputs': [ - {'amount': 500, 'script': {'condition': aux_cond}}, - {'amount': 500, 'script': {'condition': aux_cond}} - ] - } - spend0_txid = submit(sign(spend0)) - assert rpc.getrawtransaction(spend0_txid) - - # Test a good fulfillment - spend1 = { - 'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': aux_cond}}], - 'outputs': [ - {'amount': 250, 'script': {'condition': aux_cond}}, - {'amount': 250, 'script': "6A0B68656C6C6F207468657265"} # OP_RETURN somedata - ] - } - spend1_txid = submit(sign(spend1)) - assert rpc.getrawtransaction(spend1_txid) - - # Test a bad fulfillment - spend2 = { - 'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': aux_cond}}], - 'outputs': [ - {'amount': 500, 'script': "6A0B68656C6C6F207468657265"} # OP_RETURN somedata - ] - } - try: - assert not submit(sign(spend2)), 'should raise an error' - except RPCError as e: - assert SCRIPT_FALSE in str(e), str(e) - - -@fanout_input(8) -def test_secp256k1_condition(inp): - ec_cond = { - 'type': 'secp256k1-sha-256', - 'publicKey': notary_pk - } - - # Create some secp256k1 outputs - spend0 = { - 'inputs': [inp], - 'outputs': [ - {'amount': 500, 'script': {'condition': ec_cond}}, - {'amount': 500, 'script': {'condition': ec_cond}} - ] - } - spend0_txid = submit(sign(spend0)) - assert rpc.getrawtransaction(spend0_txid) - - # Test a good fulfillment - spend1 = { - 'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': ec_cond}}], - 'outputs': [{'amount': 500, 'script': {'condition': ec_cond}}] - } - spend1_txid = submit(sign(spend1)) - assert rpc.getrawtransaction(spend1_txid) - - # Test a bad fulfillment - spend2 = { - 'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': ec_cond}}], - 'outputs': [{'amount': 500, 'script': {'condition': ec_cond}}] - } - signed = sign(spend2) - signed['inputs'][0]['script']['fulfillment']['publicKey'] = \ - '0275cef12fc5c49be64f5aab3d1fbba08cd7b0d02908b5112fbd8504218d14bc7d' - try: - assert not submit(signed), 'should raise an error' - except RPCError as e: - assert SCRIPT_FALSE in str(e), str(e) - - -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - for name, f in globals().items(): - if name.startswith('test_'): - logging.info("Running test: %s" % name) - f() - logging.info("Test OK: %s" % name) diff --git a/qa/cryptoconditions/testsupport.py b/qa/cryptoconditions/testsupport.py deleted file mode 100644 index e22520f3f..000000000 --- a/qa/cryptoconditions/testsupport.py +++ /dev/null @@ -1,151 +0,0 @@ -from __future__ import print_function -import sys -import json -import time -import copy -import base64 -import logging -import functools -import subprocess - - -class RPCError(IOError): - pass - - -class JsonClient(object): - def __getattr__(self, method): - if method[0] == '_': - return getattr(super(JsonClient, self), method) - def inner(*args): - return self._exec(method, args) - return inner - - def load_response(self, data): - data = json.loads(data.decode("utf-8")) - if data.get('error'): - raise RPCError(data['error']) - if 'result' in data: - return data['result'] - return data - - -def run_cmd(cmd): - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) - assert proc.wait() == 0 - return proc.stdout.read() - - -def to_hex(s): - return base64.b16encode(s).decode('utf-8') - - -class Hoek(JsonClient): - def _exec(self, method, args): - cmd = ['hoek', method, json.dumps(args[0])] - return self.load_response(run_cmd(cmd)) - - -class Komodod(JsonClient): - def _exec(self, method, args): - if not hasattr(self, '_url'): - urltpl = 'http://$rpcuser:$rpcpassword@${rpchost:-127.0.0.1}:$rpcport' - cmd = ['bash', '-c', '. $KOMODO_CONF_PATH && echo -n %s' % urltpl] - self._url = run_cmd(cmd) - - req = {'method': method, 'params': args, 'id': 1} - cmd = ['curl', '-s', '-H', 'Content-Type: application/json', '-d', json.dumps(req), self._url] - return self.load_response(run_cmd(cmd)) - - -rpc = Komodod() -hoek = Hoek() - - -def wait_for_block(height): - logging.info("Waiting for block height %s" % height) - for i in range(100): - try: - return rpc.getblock(str(height)) - except RPCError as e: - time.sleep(3) - raise Exception('Time out waiting for block at height %s' % height) - - -def sign(tx): - signed = hoek.signTxBitcoin({'tx': tx, 'privateKeys': [notary_sk]}) - signed = hoek.signTxEd25519({'tx': signed, 'privateKeys': [alice_sk, bob_sk]}) - return hoek.signTxSecp256k1({'tx': signed, 'privateKeys': [notary_sk]}) - - -def submit(tx): - encoded = hoek.encodeTx(tx) - try: - rpc.getrawtransaction(encoded['txid']) - logging.info("Transaction already in chain: %s" % encoded['txid']) - return encoded['txid'] - except RPCError: - pass - logging.info("submit transaction: %s:%s" % (encoded['txid'], json.dumps(tx))) - return rpc.sendrawtransaction(encoded['hex']) - - -def get_fanout_txid(): - block = wait_for_block(1) - reward_txid = block['tx'][0] - reward_tx_raw = rpc.getrawtransaction(reward_txid) - reward_tx = hoek.decodeTx({'hex': reward_tx_raw}) - balance = reward_tx['outputs'][0]['amount'] - - n_outs = 16 - remainder = balance - n_outs * 1000 - - fanout = { - 'inputs': [ - {'txid': reward_txid, 'idx': 0, 'script': {'pubkey': notary_pk}} - ], - "outputs": (n_outs * [ - {"amount": 1000, "script": {"condition": cond_alice}} - ] + [{"amount": remainder, 'script': {'address': notary_addr}}]) - } - - return submit(sign(fanout)) - - -def fanout_input(n): - def decorate(f): - def wrapper(): - if not hasattr(fanout_input, 'txid'): - fanout_input.txid = get_fanout_txid() - inp = {'txid': fanout_input.txid, 'idx': n, 'script': {'fulfillment': cond_alice}} - f(copy.deepcopy(inp)) - return functools.wraps(f)(wrapper) - return decorate - - -def decode_base64(data): - """Decode base64, padding being optional. - - :param data: Base64 data as an ASCII byte string - :returns: The decoded byte string. - """ - missing_padding = len(data) % 4 - if missing_padding: - data += '=' * (4 - missing_padding) - return base64.urlsafe_b64decode(data) - - -def encode_base64(data): - return base64.urlsafe_b64encode(data).rstrip(b'=') - - -notary_addr = 'RXSwmXKtDURwXP7sdqNfsJ6Ga8RaxTchxE' -notary_pk = '0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47' -notary_sk = 'UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU' -alice_pk = '8ryTLBMnozUK4xUz7y49fjzZhxDDMK7c4mucLdbVY6jW' -alice_sk = 'E4ER7uYvaSTdpQFzTXNNSTkR6jNRJyqhZPJMGuU899nY' -cond_alice = {"type": "ed25519-sha-256", "publicKey": alice_pk} -bob_pk = 'C8MfEjKiFxDguacHvcM7MV83cRQ55RAHacC73xqg8qeu' -bob_sk = 'GrP1fZdUxUc1NYmu7kiNkJV4p7PKpshp1yBY7hogPUWT' -cond_bob = {"type": "ed25519-sha-256", "publicKey": bob_pk} -nospend = {"amount": 1000, "script": {"address": notary_addr}} diff --git a/src/Makefile.am b/src/Makefile.am index 7c64dcfd9..092015600 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -620,9 +620,10 @@ endif @test -f $(PROTOC) $(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(abspath $(method, "testEval") == 0) { + if (strcmp(cond->method, "TestEval") == 0) { return cond->paramsBinLength == 8 && - memcmp(cond->paramsBin, "testEval", 8) == 0; + memcmp(cond->paramsBin, "TestEval", 8) == 0; } if (strcmp(cond->method, "ImportPayout") == 0) { diff --git a/src/cryptoconditions/include/cryptoconditions.h b/src/cryptoconditions/include/cryptoconditions.h index 64cd1db70..a911ffd79 100644 --- a/src/cryptoconditions/include/cryptoconditions.h +++ b/src/cryptoconditions/include/cryptoconditions.h @@ -26,6 +26,7 @@ enum CCTypeId { }; + /* * Evaliliary verification callback */ @@ -83,17 +84,17 @@ int cc_signTreeSecp256k1Msg32(CC *cond, const unsigned char *private size_t cc_conditionBinary(const CC *cond, unsigned char *buf); size_t cc_fulfillmentBinary(const CC *cond, unsigned char *buf, size_t bufLength); static int cc_secp256k1VerifyTreeMsg32(const CC *cond, const unsigned char *msg32); -struct CC* cc_conditionFromJSON(cJSON *params, unsigned char *err); -struct CC* cc_conditionFromJSONString(const unsigned char *json, unsigned char *err); +struct CC* cc_conditionFromJSON(cJSON *params, char *err); +struct CC* cc_conditionFromJSONString(const char *json, char *err); struct CC* cc_readConditionBinary(const unsigned char *cond_bin, size_t cond_bin_len); struct CC* cc_readFulfillmentBinary(const unsigned char *ffill_bin, size_t ffill_bin_len); struct cJSON* cc_conditionToJSON(const CC *cond); -unsigned char* cc_conditionToJSONString(const CC *cond); -unsigned char* cc_conditionUri(const CC *cond); -unsigned char* cc_jsonRPC(unsigned char *request); -unsigned long cc_getCost(const CC *cond); -enum CCTypeId cc_typeId(const CC *cond); +char* cc_conditionToJSONString(const CC *cond); +char* cc_conditionUri(const CC *cond); +char* cc_jsonRPC(char *request); char* cc_typeName(const CC *cond); +enum CCTypeId cc_typeId(const CC *cond); +unsigned long cc_getCost(const CC *cond); uint32_t cc_typeMask(const CC *cond); void cc_free(struct CC *cond); diff --git a/src/cryptoconditions/src/cryptoconditions.c b/src/cryptoconditions/src/cryptoconditions.c index 164d58f9c..037946a5f 100644 --- a/src/cryptoconditions/src/cryptoconditions.c +++ b/src/cryptoconditions/src/cryptoconditions.c @@ -16,7 +16,7 @@ #include -static struct CCType *typeRegistry[] = { +struct CCType *CCTypeRegistry[] = { &cc_preimageType, &cc_prefixType, &cc_thresholdType, @@ -28,7 +28,7 @@ static struct CCType *typeRegistry[] = { }; -static int typeRegistryLength = sizeof(typeRegistry) / sizeof(typeRegistry[0]); +int CCTypeRegistryLength = sizeof(CCTypeRegistry) / sizeof(CCTypeRegistry[0]); void appendUriSubtypes(uint32_t mask, unsigned char *buf) { @@ -37,10 +37,10 @@ void appendUriSubtypes(uint32_t mask, unsigned char *buf) { if (mask & 1 << i) { if (append) { strcat(buf, ","); - strcat(buf, typeRegistry[i]->name); + strcat(buf, CCTypeRegistry[i]->name); } else { strcat(buf, "&subtypes="); - strcat(buf, typeRegistry[i]->name); + strcat(buf, CCTypeRegistry[i]->name); append = 1; } } @@ -48,7 +48,7 @@ void appendUriSubtypes(uint32_t mask, unsigned char *buf) { } -unsigned char *cc_conditionUri(const CC *cond) { +char *cc_conditionUri(const CC *cond) { unsigned char *fp = cond->type->fingerprint(cond); if (!fp) return NULL; @@ -158,9 +158,9 @@ unsigned long cc_getCost(const CC *cond) { CCType *getTypeByAsnEnum(Condition_PR present) { - for (int i=0; iasnType == present) { - return typeRegistry[i]; + for (int i=0; iasnType == present) { + return CCTypeRegistry[i]; } } return NULL; @@ -245,7 +245,7 @@ CC *cc_readConditionBinary(const unsigned char *cond_bin, size_t length) { asn_dec_rval_t rval; rval = ber_decode(0, &asn_DEF_Condition, (void **)&asnCond, cond_bin, length); if (rval.code != RC_OK) { - printf("Failed reading condition binary\n"); + fprintf(stderr, "Failed reading condition binary\n"); return NULL; } CC *cond = mkAnon(asnCond); diff --git a/src/cryptoconditions/src/internal.h b/src/cryptoconditions/src/internal.h index 28b3661f4..e4cdd49ba 100644 --- a/src/cryptoconditions/src/internal.h +++ b/src/cryptoconditions/src/internal.h @@ -20,13 +20,13 @@ extern "C" { * Condition Type */ typedef struct CCType { int typeId; - unsigned char name[100]; + char name[100]; Condition_PR asnType; int (*visitChildren)(CC *cond, CCVisitor visitor); unsigned char *(*fingerprint)(const CC *cond); unsigned long (*getCost)(const CC *cond); uint32_t (*getSubtypes)(const CC *cond); - CC *(*fromJSON)(const cJSON *params, unsigned char *err); + CC *(*fromJSON)(const cJSON *params, char *err); void (*toJSON)(const CC *cond, cJSON *params); CC *(*fromFulfillment)(const Fulfillment_t *ffill); Fulfillment_t *(*toFulfillment)(const CC *cond); @@ -38,8 +38,8 @@ typedef struct CCType { /* * Globals */ -static struct CCType *typeRegistry[]; -static int typeRegistryLength; +struct CCType *CCTypeRegistry[]; +int CCTypeRegistryLength; /* @@ -50,7 +50,7 @@ static CC *mkAnon(const Condition_t *asnCond); static void asnCondition(const CC *cond, Condition_t *asn); static Condition_t *asnConditionNew(const CC *cond); static Fulfillment_t *asnFulfillmentNew(const CC *cond); -static cJSON *jsonEncodeCondition(cJSON *params, unsigned char *err); +static cJSON *jsonEncodeCondition(cJSON *params, char *err); static struct CC *fulfillmentToCC(Fulfillment_t *ffill); static struct CCType *getTypeByAsnEnum(Condition_PR present); @@ -62,11 +62,11 @@ unsigned char *base64_encode(const unsigned char *data, size_t input_length); unsigned char *base64_decode(const unsigned char *data_, size_t *output_length); unsigned char *hashFingerprintContents(asn_TYPE_descriptor_t *asnType, void *fp); void dumpStr(unsigned char *str, size_t len); -int checkString(const cJSON *value, unsigned char *key, unsigned char *err); -int checkDecodeBase64(const cJSON *value, unsigned char *key, unsigned char *err, unsigned char **data, size_t *size); -int jsonGetBase64(const cJSON *params, unsigned char *key, unsigned char *err, unsigned char **data, size_t *size); -int jsonGetBase64Optional(const cJSON *params, unsigned char *key, unsigned char *err, unsigned char **data, size_t *size); -void jsonAddBase64(cJSON *params, unsigned char *key, unsigned char *bin, size_t size); +int checkString(const cJSON *value, char *key, char *err); +int checkDecodeBase64(const cJSON *value, char *key, char *err, unsigned char **data, size_t *size); +int jsonGetBase64(const cJSON *params, char *key, char *err, unsigned char **data, size_t *size); +int jsonGetBase64Optional(const cJSON *params, char *key, char *err, unsigned char **data, size_t *size); +void jsonAddBase64(cJSON *params, char *key, unsigned char *bin, size_t size); #ifdef __cplusplus diff --git a/src/cryptoconditions/src/json_rpc.c b/src/cryptoconditions/src/json_rpc.c index 8fcf47fdb..13972ca35 100644 --- a/src/cryptoconditions/src/json_rpc.c +++ b/src/cryptoconditions/src/json_rpc.c @@ -34,7 +34,7 @@ static cJSON *jsonFulfillment(CC *cond) { } -CC *cc_conditionFromJSON(cJSON *params, unsigned char *err) { +CC *cc_conditionFromJSON(cJSON *params, char *err) { if (!params || !cJSON_IsObject(params)) { strcpy(err, "Condition params must be an object"); return NULL; @@ -44,10 +44,10 @@ CC *cc_conditionFromJSON(cJSON *params, unsigned char *err) { strcpy(err, "\"type\" must be a string"); return NULL; } - for (int i=0; ivaluestring, typeRegistry[i]->name)) { - return typeRegistry[i]->fromJSON(params, err); + for (int i=0; ivaluestring, CCTypeRegistry[i]->name)) { + return CCTypeRegistry[i]->fromJSON(params, err); } } } @@ -56,7 +56,7 @@ CC *cc_conditionFromJSON(cJSON *params, unsigned char *err) { } -CC *cc_conditionFromJSONString(const unsigned char *data, unsigned char *err) { +CC *cc_conditionFromJSONString(const char *data, char *err) { cJSON *params = cJSON_Parse(data); CC *out = cc_conditionFromJSON(params, err); cJSON_Delete(params); @@ -64,7 +64,7 @@ CC *cc_conditionFromJSONString(const unsigned char *data, unsigned char *err) { } -static cJSON *jsonEncodeCondition(cJSON *params, unsigned char *err) { +static cJSON *jsonEncodeCondition(cJSON *params, char *err) { CC *cond = cc_conditionFromJSON(params, err); cJSON *out = NULL; if (cond != NULL) { @@ -75,7 +75,7 @@ static cJSON *jsonEncodeCondition(cJSON *params, unsigned char *err) { } -static cJSON *jsonEncodeFulfillment(cJSON *params, unsigned char *err) { +static cJSON *jsonEncodeFulfillment(cJSON *params, char *err) { CC *cond = cc_conditionFromJSON(params, err); cJSON *out = NULL; if (cond != NULL) { @@ -86,14 +86,14 @@ static cJSON *jsonEncodeFulfillment(cJSON *params, unsigned char *err) { } -static cJSON *jsonErr(unsigned char *err) { +static cJSON *jsonErr(char *err) { cJSON *out = cJSON_CreateObject(); cJSON_AddItemToObject(out, "error", cJSON_CreateString(err)); return out; } -static cJSON *jsonVerifyFulfillment(cJSON *params, unsigned char *err) { +static cJSON *jsonVerifyFulfillment(cJSON *params, char *err) { unsigned char *ffill_bin = 0, *msg = 0, *cond_bin = 0; size_t ffill_bin_len, msg_len, cond_bin_len; cJSON *out = 0; @@ -121,7 +121,7 @@ END: } -static cJSON *jsonDecodeFulfillment(cJSON *params, unsigned char *err) { +static cJSON *jsonDecodeFulfillment(cJSON *params, char *err) { size_t ffill_bin_len; unsigned char *ffill_bin; if (!jsonGetBase64(params, "fulfillment", err, &ffill_bin, &ffill_bin_len)) @@ -139,7 +139,7 @@ static cJSON *jsonDecodeFulfillment(cJSON *params, unsigned char *err) { } -static cJSON *jsonDecodeCondition(cJSON *params, unsigned char *err) { +static cJSON *jsonDecodeCondition(cJSON *params, char *err) { size_t cond_bin_len; unsigned char *cond_bin; if (!jsonGetBase64(params, "bin", err, &cond_bin, &cond_bin_len)) @@ -160,7 +160,7 @@ static cJSON *jsonDecodeCondition(cJSON *params, unsigned char *err) { } -static cJSON *jsonSignTreeEd25519(cJSON *params, unsigned char *err) { +static cJSON *jsonSignTreeEd25519(cJSON *params, char *err) { cJSON *out = 0; unsigned char *msg = 0, *sk = 0; @@ -197,7 +197,7 @@ END: } -static cJSON *jsonSignTreeSecp256k1(cJSON *params, unsigned char *err) { +static cJSON *jsonSignTreeSecp256k1(cJSON *params, char *err) { cJSON *out = 0; unsigned char *msg = 0, *sk = 0; @@ -244,22 +244,22 @@ cJSON *cc_conditionToJSON(const CC *cond) { } -unsigned char *cc_conditionToJSONString(const CC *cond) { +char *cc_conditionToJSONString(const CC *cond) { assert(cond != NULL); cJSON *params = cc_conditionToJSON(cond); - unsigned char *out = cJSON_Print(params); + char *out = cJSON_Print(params); cJSON_Delete(params); return out; } -static cJSON *jsonListMethods(cJSON *params, unsigned char *err); +static cJSON *jsonListMethods(cJSON *params, char *err); typedef struct JsonMethod { - unsigned char *name; - cJSON* (*method)(cJSON *params, unsigned char *err); - unsigned char *description; + char *name; + cJSON* (*method)(cJSON *params, char *err); + char *description; } JsonMethod; @@ -278,7 +278,7 @@ static JsonMethod cc_jsonMethods[] = { static int nJsonMethods = sizeof(cc_jsonMethods) / sizeof(*cc_jsonMethods); -static cJSON *jsonListMethods(cJSON *params, unsigned char *err) { +static cJSON *jsonListMethods(cJSON *params, char *err) { cJSON *list = cJSON_CreateArray(); for (int i=0; itype->typeId != cc_secp256k1Type.typeId) return 1; + if (cond->type->typeId != CC_Secp256k1) return 1; CCSecp256k1SigningData *signing = (CCSecp256k1SigningData*) visitor.context; if (0 != memcmp(cond->publicKey, signing->pk, SECP256K1_PK_SIZE)) return 1; @@ -148,7 +148,7 @@ static int secp256k1Sign(CC *cond, CCVisitor visitor) { * Sign secp256k1 conditions in a tree */ int cc_signTreeSecp256k1Msg32(CC *cond, const unsigned char *privateKey, const unsigned char *msg32) { - if (cc_typeMask(cond) & (1 << cc_preimageType.typeId)) { + if (cc_typeMask(cond) & (1 << CC_Prefix)) { // No support for prefix currently, due to pending protocol decision on // how to combine message and prefix into 32 byte hash return 0; @@ -159,7 +159,10 @@ int cc_signTreeSecp256k1Msg32(CC *cond, const unsigned char *privateKey, const u lockSign(); int rc = secp256k1_ec_pubkey_create(ec_ctx_sign, &spk, privateKey); unlockSign(); - if (rc != 1) return 0; + if (rc != 1) { + fprintf(stderr, "Cryptoconditions couldn't derive secp256k1 pubkey\n"); + return 0; + } // serialize pubkey unsigned char *publicKey = calloc(1, SECP256K1_PK_SIZE); diff --git a/src/cryptoconditions/src/threshold.c b/src/cryptoconditions/src/threshold.c index d79c01813..1efe9f9e8 100644 --- a/src/cryptoconditions/src/threshold.c +++ b/src/cryptoconditions/src/threshold.c @@ -44,7 +44,7 @@ static unsigned long thresholdCost(const CC *cond) { static int thresholdVisitChildren(CC *cond, CCVisitor visitor) { - for (int i=0; ithreshold; i++) { + for (int i=0; isize; i++) { if (!cc_visit(cond->subconditions[i], visitor)) { return 0; } @@ -203,7 +203,7 @@ static void thresholdToJSON(const CC *cond, cJSON *params) { static int thresholdIsFulfilled(const CC *cond) { int nFulfilled = 0; for (int i=0; ithreshold; i++) { - if (!cc_isFulfilled(cond->subconditions[i])) { + if (cc_isFulfilled(cond->subconditions[i])) { nFulfilled++; } if (nFulfilled == cond->threshold) { diff --git a/src/cryptoconditions/src/utils.c b/src/cryptoconditions/src/utils.c index 69db32042..c87e8ddda 100644 --- a/src/cryptoconditions/src/utils.c +++ b/src/cryptoconditions/src/utils.c @@ -141,7 +141,7 @@ void dumpStr(unsigned char *str, size_t len) { -int checkString(const cJSON *value, unsigned char *key, unsigned char *err) { +int checkString(const cJSON *value, char *key, char *err) { if (value == NULL) { sprintf(err, "%s is required", key); return 0; @@ -153,7 +153,7 @@ int checkString(const cJSON *value, unsigned char *key, unsigned char *err) { return 1; } -int checkDecodeBase64(const cJSON *value, unsigned char *key, unsigned char *err, unsigned char **data, size_t *size) { +int checkDecodeBase64(const cJSON *value, char *key, char *err, unsigned char **data, size_t *size) { if (!checkString(value, key, err)) { sprintf(err, "%s must be valid base64 string", key); return 0; @@ -168,7 +168,7 @@ int checkDecodeBase64(const cJSON *value, unsigned char *key, unsigned char *err } -int jsonGetBase64(const cJSON *params, unsigned char *key, unsigned char *err, unsigned char **data, size_t *size) +int jsonGetBase64(const cJSON *params, char *key, char *err, unsigned char **data, size_t *size) { cJSON *item = cJSON_GetObjectItem(params, key); if (!item) { @@ -179,7 +179,7 @@ int jsonGetBase64(const cJSON *params, unsigned char *key, unsigned char *err, u } -int jsonGetBase64Optional(const cJSON *params, unsigned char *key, unsigned char *err, unsigned char **data, size_t *size) { +int jsonGetBase64Optional(const cJSON *params, char *key, char *err, unsigned char **data, size_t *size) { cJSON *item = cJSON_GetObjectItem(params, key); if (!item) { return 1; @@ -188,7 +188,7 @@ int jsonGetBase64Optional(const cJSON *params, unsigned char *key, unsigned char } -void jsonAddBase64(cJSON *params, unsigned char *key, unsigned char *bin, size_t size) { +void jsonAddBase64(cJSON *params, char *key, unsigned char *bin, size_t size) { unsigned char *b64 = base64_encode(bin, size); cJSON_AddItemToObject(params, key, cJSON_CreateString(b64)); free(b64); diff --git a/src/komodo_cc.cpp b/src/komodo_cc.cpp index 1b05ea070..81459fe2b 100644 --- a/src/komodo_cc.cpp +++ b/src/komodo_cc.cpp @@ -7,18 +7,6 @@ bool IsCryptoConditionsEnabled() return 0 != ASSETCHAINS_CC; } -// Limit acceptable condition types -// Prefix not enabled because no current use case, ambiguity on how to combine with secp256k1 -// RSA not enabled because no current use case, not implemented -int CCEnabledTypes = 1 << CC_Secp256k1 | \ - 1 << CC_Threshold | \ - 1 << CC_Eval | \ - 1 << CC_Preimage | \ - 1 << CC_Ed25519; - - -int CCSigningNodes = 1 << CC_Ed25519 | 1 << CC_Secp256k1; - bool IsSupportedCryptoCondition(const CC *cond) { diff --git a/src/komodo_cc.h b/src/komodo_cc.h index 57d24f92e..62e584a94 100644 --- a/src/komodo_cc.h +++ b/src/komodo_cc.h @@ -7,6 +7,17 @@ extern int32_t ASSETCHAINS_CC; bool IsCryptoConditionsEnabled(); +// Limit acceptable condition types +// Prefix not enabled because no current use case, ambiguity on how to combine with secp256k1 +// RSA not enabled because no current use case, not implemented +const int CCEnabledTypes = 1 << CC_Secp256k1 | \ + 1 << CC_Threshold | \ + 1 << CC_Eval | \ + 1 << CC_Preimage | \ + 1 << CC_Ed25519; + +const int CCSigningNodes = 1 << CC_Ed25519 | 1 << CC_Secp256k1; + /* * Check if the server can accept the condition based on it's structure / types diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 699d6e78c..57051f797 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -161,7 +161,7 @@ public: const std::vector& ffillBin, const CScript& scriptCode, uint32_t consensusBranchId) const; - VerifyEval GetCCEval() const; + virtual VerifyEval GetCCEval() const; }; class MutableTransactionSignatureChecker : public TransactionSignatureChecker diff --git a/src/test-komodo/main.cpp b/src/test-komodo/main.cpp new file mode 100644 index 000000000..2a83e47fa --- /dev/null +++ b/src/test-komodo/main.cpp @@ -0,0 +1,12 @@ +#include "key.h" +#include "gtest/gtest.h" +#include "crypto/common.h" + + +int main(int argc, char **argv) { + assert(init_and_check_sodium() != -1); + ECC_Start(); + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/test-komodo/test_cryptoconditions.cpp b/src/test-komodo/test_cryptoconditions.cpp new file mode 100644 index 000000000..9ab4b676c --- /dev/null +++ b/src/test-komodo/test_cryptoconditions.cpp @@ -0,0 +1,226 @@ +#include +#include + +#include "base58.h" +#include "key.h" +#include "komodo_cc.h" +#include "primitives/transaction.h" +#include "script/interpreter.h" +#include "script/serverchecker.h" + + +#define VCH(a,b) std::vector(a, a + b) + +std::string pubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47"; +std::string secret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU"; +CKey notaryKey; + + +char ccjsonerr[1000] = "\0"; +#define CCFromJson(o,s) \ + o = cc_conditionFromJSONString(s, ccjsonerr); \ + if (!o) FAIL() << "bad json: " << ccjsonerr; + + +CScript CCPubKey(const CC *cond) { + unsigned char buf[1000]; + size_t len = cc_conditionBinary(cond, buf); + return CScript() << VCH(buf, len) << OP_CHECKCRYPTOCONDITION; +} + + +CScript CCSig(const CC *cond) { + unsigned char buf[1000]; + size_t len = cc_fulfillmentBinary(cond, buf, 1000); + auto ffill = VCH(buf, len); + ffill.push_back(SIGHASH_ALL); + return CScript() << ffill; +} + + +void CCSign(CMutableTransaction &tx, CC *cond) { + tx.vin.resize(1); + PrecomputedTransactionData txdata(tx); + uint256 sighash = SignatureHash(CCPubKey(cond), tx, 0, SIGHASH_ALL, 0, 0, &txdata); + + int out = cc_signTreeSecp256k1Msg32(cond, notaryKey.begin(), sighash.begin()); + tx.vin[0].scriptSig = CCSig(cond); +} + + +class CCTest : public ::testing::Test { +protected: + static void SetUpTestCase() { + SelectParams(CBaseChainParams::REGTEST); + // Notary key + CBitcoinSecret vchSecret; + // this returns false due to network prefix mismatch but works anyway + vchSecret.SetString(secret); + notaryKey = vchSecret.GetKey(); + } + virtual void SetUp() { + // enable CC + ASSETCHAINS_CC = 1; + } +}; + + +TEST_F(CCTest, testIsPayToCryptoCondition) +{ + CScript s = CScript() << VCH("a", 1); + ASSERT_FALSE(s.IsPayToCryptoCondition()); + + s = CScript() << VCH("a", 1) << OP_CHECKCRYPTOCONDITION; + ASSERT_TRUE(s.IsPayToCryptoCondition()); + + s = CScript() << OP_CHECKCRYPTOCONDITION; + ASSERT_FALSE(s.IsPayToCryptoCondition()); +} + + +TEST_F(CCTest, testMayAcceptCryptoCondition) +{ + CC *cond; + + // ok + CCFromJson(cond, R"!!( + { "type": "threshold-sha-256", + "threshold": 2, + "subfulfillments": [ + { "type": "secp256k1-sha-256", "publicKey": "AgWorQwdvFFfFJrzd5gaq1i4Nq8AjU16shvXb6+AVQtH" } + ] + })!!"); + ASSERT_TRUE(CCPubKey(cond).MayAcceptCryptoCondition()); + + + // prefix not allowed + CCFromJson(cond, R"!!( + { "type": "prefix-sha-256", + "prefix": "abc", + "maxMessageLength": 10, + "subfulfillment": + { "type": "secp256k1-sha-256", "publicKey": "AgWorQwdvFFfFJrzd5gaq1i4Nq8AjU16shvXb6+AVQtH" } + })!!"); + ASSERT_FALSE(CCPubKey(cond).MayAcceptCryptoCondition()); + + + // has no signature nodes + CCFromJson(cond, R"!!( + { "type": "threshold-sha-256", + "threshold": 1, + "subfulfillments": [ + { "type": "eval-sha-256", "method": "test", "params": "" }, + { "type": "eval-sha-256", "method": "test", "params": "" } + ] + })!!"); + ASSERT_FALSE(CCPubKey(cond).MayAcceptCryptoCondition()); +} + + +TEST_F(CCTest, testVerifyCryptoCondition) +{ + CC *cond; + ScriptError error; + CMutableTransaction mtxTo; + + auto Verify = [&] (const CC *cond) { + CAmount amount; + CTransaction txTo(mtxTo); + PrecomputedTransactionData txdata(txTo); + auto checker = ServerTransactionSignatureChecker(&txTo, 0, amount, false, txdata); + return VerifyScript(CCSig(cond), CCPubKey(cond), 0, checker, 0, &error); + }; + + // ok + CCFromJson(cond, R"!!({ + "type": "secp256k1-sha-256", + "publicKey": "AgWorQwdvFFfFJrzd5gaq1i4Nq8AjU16shvXb6+AVQtH" + })!!"); + CCSign(mtxTo, cond); + ASSERT_TRUE(Verify(cond)); + + + // has signature nodes + CCFromJson(cond, R"!!({ + "type": "threshold-sha-256", + "threshold": 1, + "subfulfillments": [ + { "type": "preimage-sha-256", "preimage": "" }, + { "type": "secp256k1-sha-256", "publicKey": "AgWorQwdvFFfFJrzd5gaq1i4Nq8AjU16shvXb6+AVQtH" } + ] + })!!"); + cond->threshold = 2; + CCSign(mtxTo, cond); + ASSERT_TRUE(Verify(cond)); + + // no signatures; the preimage will get encoded as a fulfillment because it's cheaper + // and the secp256k1 node will get encoded as a condition + cond->threshold = 1; + ASSERT_FALSE(Verify(cond)); + + // here the signature is set wrong + cond->threshold = 2; + ASSERT_TRUE(Verify(cond)); + memset(cond->subconditions[1]->signature, 0, 32); + ASSERT_FALSE(Verify(cond)); +} + + +TEST_F(CCTest, testVerifyEvalCondition) +{ + CC *cond; + ScriptError error; + CMutableTransaction mtxTo; + + auto Verify = [&] (const CC *cond) { + CAmount amount; + CTransaction txTo(mtxTo); + PrecomputedTransactionData txdata(txTo); + auto checker = ServerTransactionSignatureChecker(&txTo, 0, amount, false, txdata); + return VerifyScript(CCSig(cond), CCPubKey(cond), 0, checker, 0, &error); + }; + + // ok + CCFromJson(cond, R"!!({ + "type": "threshold-sha-256", + "threshold": 2, + "subfulfillments": [ + { "type": "secp256k1-sha-256", "publicKey": "AgWorQwdvFFfFJrzd5gaq1i4Nq8AjU16shvXb6+AVQtH" }, + { "type": "eval-sha-256", "method": "TestEval", "params": "" } + ]})!!"); + CC *ecCond = cond->subconditions[1]; + ecCond->paramsBin = (unsigned char*) "TestEval"; + ecCond->paramsBinLength = 8; + CCSign(mtxTo, cond); // will reorder subconditions + ASSERT_TRUE(Verify(cond)); + + ecCond->paramsBin = (unsigned char*) "FailEval"; + ASSERT_FALSE(Verify(cond)); +} + + +TEST_F(CCTest, testCryptoConditionsDisabled) +{ + CC *cond; + ScriptError error; + CMutableTransaction mtxTo; + + auto Verify = [&] (const CC *cond) { + CAmount amount; + CTransaction txTo(mtxTo); + PrecomputedTransactionData txdata(txTo); + auto checker = ServerTransactionSignatureChecker(&txTo, 0, amount, false, txdata); + return VerifyScript(CCSig(cond), CCPubKey(cond), 0, checker, 0, &error); + }; + + // ok + CCFromJson(cond, R"!!({ + "type": "secp256k1-sha-256", + "publicKey": "AgWorQwdvFFfFJrzd5gaq1i4Nq8AjU16shvXb6+AVQtH" + })!!"); + CCSign(mtxTo, cond); + ASSERT_TRUE(Verify(cond)); + + ASSETCHAINS_CC = 0; + ASSERT_FALSE(Verify(cond)); +}