/****************************************************************************** * Copyright © 2014-2019 The SuperNET Developers. * * * * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * SuperNET software, including this file may be copied, modified, propagated * * or distributed except according to the terms contained in the LICENSE file * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ /* first make a combined pk: ./c cclib combine 18 \"[%2202aff51dad774a1c612dc82e63f85f07b992b665836b0f0efbcb26ee679f4f4848%22,%22039433dc3749aece1bd568f374a45da3b0bc6856990d7da3cd175399577940a775%22]\" { "pkhash": "5be117f3c5ce87e7dc6882c24b8231e0652ee82054bf7b9f94aef1f45e055cba", "combined_pk": "032ddac56613cd0667b589bd7f32b665e2d2ce0247e337a5a0bca6c72e3d9d057b", "result": "success" } */ /* second, send 0.777 coins to the combined_pk ./c cclib send 18 \"[%22032ddac56613cd0667b589bd7f32b665e2d2ce0247e337a5a0bca6c72e3d9d057b%22,0.777]\" { "hex": "0400008085202f8901bf7afcbbab7a5e1ba20c8010325e199305afa7d15b4bc86a589a512201d39924000000004847304402203e0cff8aaa831dd990afd979bbc0a648316f77407cb5e02780d18e0670f5172a0220283d5c52d0406b10c508cc2b19dd6dbe2420e7c5cf431a1d41072d0eec28edb901ffffffff03b0c2a10400000000302ea22c8020c71ddb3aac7f9b9e4bdacf032aaa8b8e4433c4ff9f8a43cebb9c1f5da96928a48103120c008203000401cca91e223700000000232102aff51dad774a1c612dc82e63f85f07b992b665836b0f0efbcb26ee679f4f4848ac0000000000000000266a24127821032ddac56613cd0667b589bd7f32b665e2d2ce0247e337a5a0bca6c72e3d9d057b00000000900000000000000000000000000000", "txid": "cb5309ed249da95e2b5696eb763a8736e2fff1d14922ada737b931494ca3d2be", "result": "success" } { "value": 0.77710000, "valueZat": 77710000, "n": 0, "scriptPubKey": { "asm": "a22c8020c71ddb3aac7f9b9e4bdacf032aaa8b8e4433c4ff9f8a43cebb9c1f5da96928a48103120c008203000401 OP_CHECKCRYPTOCONDITION", "hex": "2ea22c8020c71ddb3aac7f9b9e4bdacf032aaa8b8e4433c4ff9f8a43cebb9c1f5da96928a48103120c008203000401cc", "reqSigs": 1, "type": "cryptocondition", "addresses": [ "RKWS7jxyjPX9iaJttk8iMKf1AumanKypez" ] } change script: 2102aff51dad774a1c612dc82e63f85f07b992b665836b0f0efbcb26ee679f4f4848ac sendtxid: cb5309ed249da95e2b5696eb763a8736e2fff1d14922ada737b931494ca3d2be broadcast sendtxid and wait for it to be confirmed. then get the msg we need to sign: ./c cclib calcmsg 18 \"[%22cb5309ed249da95e2b5696eb763a8736e2fff1d14922ada737b931494ca3d2be%22,%222102aff51dad774a1c612dc82e63f85f07b992b665836b0f0efbcb26ee679f4f4848ac%22]\" { "result": "success", "msg": "63b799913d4c9487f321b32d6ae8614f653f38e0b50d4df4bc1d36339ea18485" } */ #define USE_BASIC_CONFIG #define ENABLE_MODULE_MUSIG #include "../secp256k1/src/basic-config.h" #include "../secp256k1/include/secp256k1.h" #include "../secp256k1/src/ecmult.h" #include "../secp256k1/src/ecmult_gen.h" typedef struct { unsigned char data[64]; } secp256k1_schnorrsig; struct secp256k1_context_struct { secp256k1_ecmult_context ecmult_ctx; secp256k1_ecmult_gen_context ecmult_gen_ctx; secp256k1_callback illegal_callback; secp256k1_callback error_callback; }; extern "C" int secp256k1_ecmult_multi_var(const secp256k1_ecmult_context *ctx, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n); extern "C" int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const secp256k1_schnorrsig *sig, const unsigned char *msg32, const secp256k1_pubkey *pk); extern "C" int secp256k1_schnorrsig_parse(const secp256k1_context* ctx, secp256k1_schnorrsig* sig, const unsigned char *in64); extern "C" int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scratch_space *scratch, secp256k1_pubkey *combined_pk, unsigned char *pk_hash32, const secp256k1_pubkey *pubkeys, size_t n_pubkeys); //#include "../secp256k1/include/secp256k1.h" //#include "../secp256k1/include/secp256k1_schnorrsig.h" #include "../secp256k1/include/secp256k1_musig.h" #define MUSIG_PREVN 0 // for now, just use vout0 for the musig output #define MUSIG_TXFEE 10000 CScript musig_sendopret(uint8_t funcid,CPubKey pk) { CScript opret; uint8_t evalcode = EVAL_MUSIG; opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << pk); return(opret); } uint8_t musig_sendopretdecode(CPubKey &pk,CScript scriptPubKey) { std::vector vopret; uint8_t e,f; GetOpReturnData(scriptPubKey,vopret); if ( vopret.size() > 2 && E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> pk) != 0 && e == EVAL_MUSIG && f == 'x' ) { return(f); } return(0); } CScript musig_spendopret(uint8_t funcid,CPubKey pk,std::vector musig64) { CScript opret; uint8_t evalcode = EVAL_MUSIG; opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << pk << musig64); return(opret); } uint8_t musig_spendopretdecode(CPubKey &pk,std::vector &musig64,CScript scriptPubKey) { std::vector vopret; uint8_t e,f; GetOpReturnData(scriptPubKey,vopret); if ( vopret.size() > 2 && E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> pk; ss >> musig64) != 0 && e == EVAL_MUSIG && f == 'y' ) { return(f); } return(0); } int32_t musig_msghash(uint8_t *msg,uint256 prevhash,int32_t prevn,CTxOut vout,CPubKey pk) { CScript data; uint256 hash; int32_t len = 0; data << E_MARSHAL(ss << prevhash << prevn << vout << pk); hash = Hash(data.begin(),data.end()); memcpy(msg,&hash,sizeof(hash)); return(0); } int32_t musig_prevoutmsg(uint8_t *msg,uint256 sendtxid,CScript scriptPubKey) { CTransaction vintx; uint256 hashBlock; int32_t numvouts; CTxOut vout; CPubKey pk; memset(msg,0,32); if ( myGetTransaction(sendtxid,vintx,hashBlock) != 0 && (numvouts= vintx.vout.size()) > 1 ) { if ( musig_sendopretdecode(pk,vintx.vout[numvouts-1].scriptPubKey) == 'x' ) { vout.nValue = vintx.vout[MUSIG_PREVN].nValue - MUSIG_TXFEE; vout.scriptPubKey = scriptPubKey; return(musig_msghash(msg,sendtxid,MUSIG_PREVN,vout,pk)); } } return(-1); } UniValue musig_calcmsg(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { UniValue result(UniValue::VOBJ); uint256 sendtxid; int32_t i; uint8_t msg[32]; char *scriptstr,str[65]; int32_t n; if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) { if ( n == 2 ) { sendtxid = juint256(jitem(params,0)); scriptstr = jstr(jitem(params,1),0); if ( is_hexstr(scriptstr,0) != 0 ) { CScript scriptPubKey(ParseHex(scriptstr)); musig_prevoutmsg(msg,sendtxid,scriptPubKey); result.push_back(Pair("result","success")); for (i=0; i<32; i++) sprintf(&str[i<<1],"%02x",msg[i]); str[64] = 0; result.push_back(Pair("msg",str)); return(result); } else return(cclib_error(result,"script is not hex")); } else return(cclib_error(result,"need exactly 2 parameters: sendtxid, scriptPubKey")); } else return(cclib_error(result,"couldnt parse params")); } UniValue musig_combine(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { static secp256k1_context *ctx; size_t clen = CPubKey::PUBLIC_KEY_SIZE; UniValue result(UniValue::VOBJ); CPubKey pk; int32_t i,n; uint8_t pkhash[32]; char *hexstr,str[67]; secp256k1_pubkey combined_pk,spk; std::vector pubkeys; if ( ctx == 0 ) ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) { //fprintf(stderr,"n.%d args.(%s)\n",n,jprint(params,0)); for (i=0; i 0 ) pubkeys.push_back(spk); else return(cclib_error(result,"error parsing pk")); } else return(cclib_error(result,"all pubkeys must be 33 bytes hexdata")); } if ( secp256k1_musig_pubkey_combine(ctx,NULL,&combined_pk,pkhash,&pubkeys[0],n) > 0 ) { if ( secp256k1_ec_pubkey_serialize(ctx,(uint8_t *)pk.begin(),&clen,&combined_pk,SECP256K1_EC_COMPRESSED) > 0 && clen == 33 ) { for (i=0; i<32; i++) sprintf(&str[i<<1],"%02x",pkhash[i]); str[64] = 0; result.push_back(Pair("pkhash",str)); for (i=0; i<33; i++) sprintf(&str[i<<1],"%02x",((uint8_t *)pk.begin())[i]); str[66] = 0; result.push_back(Pair("combined_pk",str)); result.push_back(Pair("result","success")); return(result); } else return(cclib_error(result,"error serializeing combined_pk")); } else return(cclib_error(result,"error combining pukbeys")); } else return(cclib_error(result,"need pubkeys params")); } UniValue musig_session(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { UniValue result(UniValue::VOBJ); result.push_back(Pair("result","success")); /** Initializes a signing session for a signer * * Returns: 1: session is successfully initialized * 0: session could not be initialized: secret key or secret nonce overflow * Args: ctx: pointer to a context object, initialized for signing (cannot * be NULL) * Out: session: the session structure to initialize (cannot be NULL) * signers: an array of signers' data to be initialized. Array length must * equal to `n_signers` (cannot be NULL) * nonce_commitment32: filled with a 32-byte commitment to the generated nonce * (cannot be NULL) * In: session_id32: a *unique* 32-byte ID to assign to this session (cannot be * NULL). If a non-unique session_id32 was given then a partial * signature will LEAK THE SECRET KEY. * msg32: the 32-byte message to be signed. Shouldn't be NULL unless you * require sharing public nonces before the message is known * because it reduces nonce misuse resistance. If NULL, must be * set with `musig_session_set_msg` before signing and verifying. * combined_pk: the combined public key of all signers (cannot be NULL) * pk_hash32: the 32-byte hash of the signers' individual keys (cannot be * NULL) * n_signers: length of signers array. Number of signers participating in * the MuSig. Must be greater than 0 and at most 2^32 - 1. * my_index: index of this signer in the signers array * seckey: the signer's 32-byte secret key (cannot be NULL) */ //if (!secp256k1_musig_session_initialize(ctx, &musig_session[i], signer_data[i], nonce_commitment[i], session_id32, msg32, &combined_pk, pk_hash, N_SIGNERS, i, seckeys[i])) //return 0; // randombytes_buf(buf, num); //nonce_commitment_ptr[i] = &nonce_commitment[i][0]; return(result); } UniValue musig_commit(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { UniValue result(UniValue::VOBJ); result.push_back(Pair("result","success")); /** Gets the signer's public nonce given a list of all signers' data with commitments * * Returns: 1: public nonce is written in nonce * 0: signer data is missing commitments or session isn't initialized * for signing * Args: ctx: pointer to a context object (cannot be NULL) * session: the signing session to get the nonce from (cannot be NULL) * signers: an array of signers' data initialized with * `musig_session_initialize`. Array length must equal to * `n_commitments` (cannot be NULL) * Out: nonce: the nonce (cannot be NULL) * In: commitments: array of 32-byte nonce commitments (cannot be NULL) * n_commitments: the length of commitments and signers array. Must be the total * number of signers participating in the MuSig. */ // Set nonce commitments in the signer data and get the own public nonce //if (!secp256k1_musig_session_get_public_nonce(ctx, &musig_session[i], signer_data[i], &nonce[i], nonce_commitment_ptr, N_SIGNERS)) // return 0; return(result); } UniValue musig_nonce(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { UniValue result(UniValue::VOBJ); result.push_back(Pair("result","success")); /** Checks a signer's public nonce against a commitment to said nonce, and update * data structure if they match * * Returns: 1: commitment was valid, data structure updated * 0: commitment was invalid, nothing happened * Args: ctx: pointer to a context object (cannot be NULL) * signer: pointer to the signer data to update (cannot be NULL). Must have * been used with `musig_session_get_public_nonce` or initialized * with `musig_session_initialize_verifier`. * In: nonce: signer's alleged public nonce (cannot be NULL) */ //if (!secp256k1_musig_set_nonce(ctx, &signer_data[i][j], &nonce[j])) { /** Updates a session with the combined public nonce of all signers. The combined * public nonce is the sum of every signer's public nonce. * * Returns: 1: nonces are successfully combined * 0: a signer's nonce is missing * Args: ctx: pointer to a context object (cannot be NULL) * session: session to update with the combined public nonce (cannot be * NULL) * signers: an array of signers' data, which must have had public nonces * set with `musig_set_nonce`. Array length must equal to `n_signers` * (cannot be NULL) * n_signers: the length of the signers array. Must be the total number of * signers participating in the MuSig. * Out: nonce_is_negated: a pointer to an integer that indicates if the combined * public nonce had to be negated. * adaptor: point to add to the combined public nonce. If NULL, nothing is * added to the combined nonce. */ // after all nonces: if (!secp256k1_musig_session_combine_nonces(ctx, &musig_session[i], signer_data[i], N_SIGNERS, NULL, NULL)) { return(result); } UniValue musig_partialsign(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { UniValue result(UniValue::VOBJ); result.push_back(Pair("result","success")); /** Produces a partial signature * * Returns: 1: partial signature constructed * 0: session in incorrect or inconsistent state * Args: ctx: pointer to a context object (cannot be NULL) * session: active signing session for which the combined nonce has been * computed (cannot be NULL) * Out: partial_sig: partial signature (cannot be NULL) */ //if (!secp256k1_musig_partial_sign(ctx, &musig_session[i], &partial_sig[i])) { /** Parse and verify a MuSig partial signature. * * Returns: 1 when the signature could be parsed, 0 otherwise. * Args: ctx: a secp256k1 context object * Out: sig: pointer to a signature object * In: in32: pointer to the 32-byte signature to be parsed * * After the call, sig will always be initialized. If parsing failed or the * encoded numbers are out of range, signature verification with it is * guaranteed to fail for every message and public key. */ //SECP256K1_API int secp256k1_musig_partial_signature_parse( return(result); } UniValue musig_sigcombine(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { UniValue result(UniValue::VOBJ); result.push_back(Pair("result","success")); /** Checks that an individual partial signature verifies * * This function is essential when using protocols with adaptor signatures. * However, it is not essential for regular MuSig's, in the sense that if any * partial signatures does not verify, the full signature will also not verify, so the * problem will be caught. But this function allows determining the specific party * who produced an invalid signature, so that signing can be restarted without them. * * Returns: 1: partial signature verifies * 0: invalid signature or bad data * Args: ctx: pointer to a context object (cannot be NULL) * session: active session for which the combined nonce has been computed * (cannot be NULL) * signer: data for the signer who produced this signature (cannot be NULL) * In: partial_sig: signature to verify (cannot be NULL) * pubkey: public key of the signer who produced the signature (cannot be NULL) */ //if (!secp256k1_musig_partial_sig_verify(ctx, &musig_session[i], &signer_data[i][j], &partial_sig[j], &pubkeys[j])) { return 0; /** Combines partial signatures * * Returns: 1: all partial signatures have values in range. Does NOT mean the * resulting signature verifies. * 0: some partial signature had s/r out of range * Args: ctx: pointer to a context object (cannot be NULL) * session: initialized session for which the combined nonce has been * computed (cannot be NULL) * Out: sig: complete signature (cannot be NULL) * In: partial_sigs: array of partial signatures to combine (cannot be NULL) * n_sigs: number of signatures in the partial_sigs array */ // after all partials: return secp256k1_musig_partial_sig_combine(ctx, &musig_session[0], sig, partial_sig, N_SIGNERS return(result); } UniValue musig_verify(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { UniValue result(UniValue::VOBJ); result.push_back(Pair("result","success")); /** Verify a Schnorr signature. * * Returns: 1: correct signature * 0: incorrect or unparseable signature * Args: ctx: a secp256k1 context object, initialized for verification. * In: sig: the signature being verified (cannot be NULL) * msg32: the 32-byte message hash being verified (cannot be NULL) * pubkey: pointer to a public key to verify with (cannot be NULL) */ // if (!secp256k1_schnorrsig_verify(ctx, &sig, msg, &combined_pk)) { return(result); } // helpers for rpc calls that generate/validate onchain tx UniValue musig_rawtxresult(UniValue &result,std::string rawtx) { CTransaction tx; if ( rawtx.size() > 0 ) { result.push_back(Pair("hex",rawtx)); if ( DecodeHexTx(tx,rawtx) != 0 ) { //if ( broadcastflag != 0 && myAddtomempool(tx) != 0 ) // RelayTransaction(tx); result.push_back(Pair("txid",tx.GetHash().ToString())); result.push_back(Pair("result","success")); } else result.push_back(Pair("error","decode hex")); } else result.push_back(Pair("error","couldnt finalize CCtx")); return(result); } UniValue musig_send(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); UniValue result(UniValue::VOBJ); int32_t n; char *hexstr; std::string rawtx; int64_t amount; CPubKey musigpk,mypk; if ( txfee == 0 ) txfee = MUSIG_TXFEE; mypk = pubkey2pk(Mypubkey()); musigpk = GetUnspendable(cp,0); if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) { if ( n == 2 && (hexstr= jstr(jitem(params,0),0)) != 0 && is_hexstr(hexstr,0) == 66 ) { CPubKey pk(ParseHex(hexstr)); amount = jdouble(jitem(params,1),0) * COIN + 0.0000000049; if ( amount >= 3*txfee && AddNormalinputs(mtx,mypk,amount+2*txfee,64) >= amount+2*txfee ) { mtx.vout.push_back(MakeCC1vout(cp->evalcode,amount+txfee,musigpk)); rawtx = FinalizeCCTx(0,cp,mtx,mypk,txfee,musig_sendopret('x',pk)); return(musig_rawtxresult(result,rawtx)); } else return(cclib_error(result,"couldnt find funds or less than 0.0003")); } else return(cclib_error(result,"must have 2 params: pk, amount")); } else return(cclib_error(result,"not enough parameters")); } UniValue musig_spend(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) { static secp256k1_context *ctx; CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); UniValue result(UniValue::VOBJ); std::string rawtx; CPubKey mypk,pk; secp256k1_pubkey combined_pk; char *scriptstr,*musigstr; uint8_t msg[32]; CTransaction vintx; uint256 prevhash,hashBlock; int32_t n,numvouts; CTxOut vout; secp256k1_schnorrsig musig; if ( ctx == 0 ) ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) { if ( n == 3 ) { prevhash = juint256(jitem(params,0)); scriptstr = jstr(jitem(params,1),0); musigstr = jstr(jitem(params,2),0); if ( is_hexstr(scriptstr,0) != 0 && is_hexstr(musigstr,0) != 128 ) { if ( txfee == 0 ) txfee = MUSIG_TXFEE; mypk = pubkey2pk(Mypubkey()); std::vector musig64(ParseHex(musigstr)); CScript scriptPubKey(ParseHex(scriptstr)); if ( myGetTransaction(prevhash,vintx,hashBlock) != 0 && (numvouts= vintx.vout.size()) > 1 ) { vout.nValue = vintx.vout[0].nValue - txfee; vout.scriptPubKey = scriptPubKey; if ( musig_sendopretdecode(pk,vintx.vout[numvouts-1].scriptPubKey) == 'x' ) { if ( secp256k1_schnorrsig_parse((const secp256k1_context *)ctx,&musig,(const uint8_t *)&musig64[0]) > 0 && secp256k1_ec_pubkey_parse(ctx,&combined_pk,pk.begin(),33) > 0 ) { musig_prevoutmsg(msg,prevhash,vout.scriptPubKey); if ( !secp256k1_schnorrsig_verify((const secp256k1_context *)ctx,&musig,(const uint8_t *)msg,(const secp256k1_pubkey *)&combined_pk) ) return(cclib_error(result,"musig didnt validate")); mtx.vin.push_back(CTxIn(prevhash,MUSIG_PREVN)); mtx.vout.push_back(vout); rawtx = FinalizeCCTx(0,cp,mtx,mypk,txfee,musig_spendopret('y',pk,musig64)); return(musig_rawtxresult(result,rawtx)); } else return(cclib_error(result,"couldnt parse pk or musig")); } else return(cclib_error(result,"couldnt decode send opret")); } else return(cclib_error(result,"couldnt find vin0")); } else return(cclib_error(result,"script or musig is not hex")); } else return(cclib_error(result,"need to have exactly 3 params prevhash, scriptPubKey, musig")); } else return(cclib_error(result,"params parse error")); } bool musig_validate(struct CCcontract_info *cp,int32_t height,Eval *eval,const CTransaction tx) { static secp256k1_context *ctx; secp256k1_pubkey combined_pk; CPubKey pk,checkpk; secp256k1_schnorrsig musig; uint256 hashBlock; CTransaction vintx; int32_t numvouts; std::vector musig64; uint8_t msg[32]; if ( ctx == 0 ) ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); if ( tx.vout.size() != 2 ) return eval->Invalid("numvouts != 2"); else if ( tx.vin.size() != 1 ) return eval->Invalid("numvins != 1"); else if ( IsCCInput(tx.vin[0].scriptSig) == 0 ) return eval->Invalid("illegal normal vin0"); else if ( myGetTransaction(tx.vin[0].prevout.hash,vintx,hashBlock) != 0 && (numvouts= vintx.vout.size()) > 1 ) { if ( musig_sendopretdecode(pk,vintx.vout[numvouts-1].scriptPubKey) == 'x' ) { if ( musig_spendopretdecode(checkpk,musig64,tx.vout[tx.vout.size()-1].scriptPubKey) == 'y' ) { if ( pk == checkpk ) { if ( secp256k1_schnorrsig_parse((const secp256k1_context *)ctx,&musig,(const uint8_t *)&musig64[0]) > 0 && secp256k1_ec_pubkey_parse(ctx,&combined_pk,pk.begin(),33) > 0 ) { musig_prevoutmsg(msg,tx.vin[0].prevout.hash,tx.vout[0].scriptPubKey); if ( !secp256k1_schnorrsig_verify((const secp256k1_context *)ctx,&musig,(const uint8_t *)msg,(const secp256k1_pubkey *)&combined_pk) ) return eval->Invalid("failed schnorrsig_verify"); else return(true); } else return eval->Invalid("couldnt parse pk or musig"); } else return eval->Invalid("combined_pk didnt match send opret"); } else return eval->Invalid("failed decode musig spendopret"); } else return eval->Invalid("couldnt decode send opret"); } else return eval->Invalid("couldnt find vin0 tx"); }