diff --git a/crypto/ellcurve/secp256k1.cpp b/crypto/ellcurve/secp256k1.cpp index e890117a4..b98eea7be 100644 --- a/crypto/ellcurve/secp256k1.cpp +++ b/crypto/ellcurve/secp256k1.cpp @@ -17,13 +17,22 @@ #include "secp256k1.h" #include "td/utils/check.h" +#include "td/utils/logging.h" + #include +#include #include -namespace td { +namespace td::secp256k1 { -bool ecrecover(const unsigned char* hash, const unsigned char* signature, unsigned char* public_key) { +static const secp256k1_context* get_context() { static secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + LOG_CHECK(ctx) << "Failed to create secp256k1_context"; + return ctx; +} + +bool ecrecover(const unsigned char* hash, const unsigned char* signature, unsigned char* public_key) { + const secp256k1_context* ctx = get_context(); secp256k1_ecdsa_recoverable_signature ecdsa_signature; if (signature[64] > 3 || !secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &ecdsa_signature, signature, signature[64])) { @@ -39,4 +48,22 @@ bool ecrecover(const unsigned char* hash, const unsigned char* signature, unsign return true; } +bool xonly_pubkey_tweak_add(const unsigned char* xonly_pubkey_bytes, const unsigned char* tweak, + unsigned char* output_pubkey_bytes) { + const secp256k1_context* ctx = get_context(); + + secp256k1_xonly_pubkey xonly_pubkey; + secp256k1_pubkey output_pubkey; + if (!secp256k1_xonly_pubkey_parse(ctx, &xonly_pubkey, xonly_pubkey_bytes)) { + return false; + } + if (!secp256k1_xonly_pubkey_tweak_add(ctx, &output_pubkey, &xonly_pubkey, tweak)) { + return false; + } + size_t len = 65; + secp256k1_ec_pubkey_serialize(ctx, output_pubkey_bytes, &len, &output_pubkey, SECP256K1_EC_UNCOMPRESSED); + CHECK(len == 65); + return true; } + +} // namespace td::secp256k1 diff --git a/crypto/ellcurve/secp256k1.h b/crypto/ellcurve/secp256k1.h index 80ab6a873..20f0b66b3 100644 --- a/crypto/ellcurve/secp256k1.h +++ b/crypto/ellcurve/secp256k1.h @@ -16,8 +16,10 @@ */ #pragma once -namespace td { +namespace td::secp256k1 { bool ecrecover(const unsigned char* hash, const unsigned char* signature, unsigned char* public_key); +bool xonly_pubkey_tweak_add(const unsigned char* xonly_pubkey_bytes, const unsigned char* tweak, + unsigned char* output_pubkey_bytes); -} +} // namespace td::secp256k1 diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 964db4417..39cb759d1 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -1015,6 +1015,10 @@ x{EDC} dup @Defop(c) SAVEBOTH @Defop(c) SAVEBOTHCTR x{EDE0} @Defop PUSHCTRX x{EDE1} @Defop POPCTRX x{EDE2} @Defop SETCONTCTRX +x{EDE3} @Defop(8u) SETCONTCTRMANY +x{EDE3} @Defop(8u) SETCONTMANY +x{EDE4} @Defop SETCONTCTRMANYX +x{EDE4} @Defop SETCONTMANYX x{EDF0} dup @Defop BOOLAND @Defop COMPOS x{EDF1} dup @Defop BOOLOR @Defop COMPOSALT x{EDF2} @Defop COMPOSBOTH @@ -1354,6 +1358,7 @@ x{F90704} @Defop HASHEXTAR_KECCAK512 x{F910} @Defop CHKSIGNU x{F911} @Defop CHKSIGNS x{F912} @Defop ECRECOVER +x{F913} @Defop SECP256K1_XONLY_PUBKEY_TWEAK_ADD x{F914} @Defop P256_CHKSIGNU x{F915} @Defop P256_CHKSIGNS diff --git a/crypto/vm/contops.cpp b/crypto/vm/contops.cpp index 9610e4aa6..3b8926586 100644 --- a/crypto/vm/contops.cpp +++ b/crypto/vm/contops.cpp @@ -923,6 +923,41 @@ int exec_setcont_ctr_var(VmState* st) { return 0; } +int exec_setcont_ctr_many(VmState* st, unsigned args) { + unsigned mask = args & 255; + VM_LOG(st) << "execute SETCONTCTRMANY " << mask; + if (mask & (1 << 6)) { + throw VmError{Excno::range_chk, "no control register c6"}; + } + Stack& stack = st->get_stack(); + auto cont = stack.pop_cont(); + for (int i = 0; i < 8; ++i) { + if (mask & (1 << i)) { + throw_typechk(force_cregs(cont)->define(i, st->get(i))); + } + } + st->get_stack().push_cont(std::move(cont)); + return 0; +} + +int exec_setcont_ctr_many_var(VmState* st) { + VM_LOG(st) << "execute SETCONTCTRMANYX"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + int mask = stack.pop_smallint_range(255); + if (mask & (1 << 6)) { + throw VmError{Excno::range_chk, "no control register c6"}; + } + auto cont = stack.pop_cont(); + for (int i = 0; i < 8; ++i) { + if (mask & (1 << i)) { + throw_typechk(force_cregs(cont)->define(i, st->get(i))); + } + } + st->get_stack().push_cont(std::move(cont)); + return 0; +} + int exec_compos(VmState* st, unsigned mask, const char* name) { Stack& stack = st->get_stack(); VM_LOG(st) << "execute " << name; @@ -1037,6 +1072,8 @@ void register_continuation_change_ops(OpcodeTable& cp0) { cp0.insert(OpcodeInstr::mksimple(0xede0, 16, "PUSHCTRX", exec_push_ctr_var)) .insert(OpcodeInstr::mksimple(0xede1, 16, "POPCTRX", exec_pop_ctr_var)) .insert(OpcodeInstr::mksimple(0xede2, 16, "SETCONTCTRX", exec_setcont_ctr_var)) + .insert(OpcodeInstr::mkfixed(0xede3, 16, 8, instr::dump_1c_l_add(1, "SETCONTCTRMANY "), exec_setcont_ctr_many)->require_version(9)) + .insert(OpcodeInstr::mksimple(0xede4, 16, "SETCONTCTRMANYX", exec_setcont_ctr_many_var)->require_version(9)) .insert(OpcodeInstr::mksimple(0xedf0, 16, "BOOLAND", std::bind(exec_compos, _1, 1, "BOOLAND"))) .insert(OpcodeInstr::mksimple(0xedf1, 16, "BOOLOR", std::bind(exec_compos, _1, 2, "BOOLOR"))) .insert(OpcodeInstr::mksimple(0xedf2, 16, "COMPOSBOTH", std::bind(exec_compos, _1, 3, "COMPOSBOTH"))) diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp index 4b2d1734e..6c698df4b 100644 --- a/crypto/vm/tonops.cpp +++ b/crypto/vm/tonops.cpp @@ -661,7 +661,38 @@ int exec_ecrecover(VmState* st) { } st->consume_gas(VmState::ecrecover_gas_price); unsigned char public_key[65]; - if (td::ecrecover(hash_bytes, signature, public_key)) { + if (td::secp256k1::ecrecover(hash_bytes, signature, public_key)) { + td::uint8 h = public_key[0]; + td::RefInt256 x1{true}, x2{true}; + CHECK(x1.write().import_bytes(public_key + 1, 32, false)); + CHECK(x2.write().import_bytes(public_key + 33, 32, false)); + stack.push_smallint(h); + stack.push_int(std::move(x1)); + stack.push_int(std::move(x2)); + stack.push_bool(true); + } else { + stack.push_bool(false); + } + return 0; +} + +int exec_secp256k1_xonly_pubkey_tweak_add(VmState* st) { + VM_LOG(st) << "execute SECP256K1_XONLY_PUBKEY_TWEAK_ADD"; + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto tweak_int = stack.pop_int(); + auto key_int = stack.pop_int(); + + unsigned char key[32], tweak[32]; + if (!key_int->export_bytes(key, 32, false)) { + throw VmError{Excno::range_chk, "key must fit in an unsigned 256-bit integer"}; + } + if (!tweak_int->export_bytes(tweak, 32, false)) { + throw VmError{Excno::range_chk, "tweak must fit in an unsigned 256-bit integer"}; + } + st->consume_gas(VmState::secp256k1_xonly_pubkey_tweak_add_gas_price); + unsigned char public_key[65]; + if (td::secp256k1::xonly_pubkey_tweak_add(key, tweak, public_key)) { td::uint8 h = public_key[0]; td::RefInt256 x1{true}, x2{true}; CHECK(x1.write().import_bytes(public_key + 1, 32, false)); @@ -1214,6 +1245,7 @@ void register_ton_crypto_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0xf910, 16, "CHKSIGNU", std::bind(exec_ed25519_check_signature, _1, false))) .insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true))) .insert(OpcodeInstr::mksimple(0xf912, 16, "ECRECOVER", exec_ecrecover)->require_version(4)) + .insert(OpcodeInstr::mksimple(0xf913, 16, "SECP256K1_XONLY_PUBKEY_TWEAK_ADD", exec_secp256k1_xonly_pubkey_tweak_add)->require_version(9)) .insert(OpcodeInstr::mksimple(0xf914, 16, "P256_CHKSIGNU", std::bind(exec_p256_chksign, _1, false))->require_version(4)) .insert(OpcodeInstr::mksimple(0xf915, 16, "P256_CHKSIGNS", std::bind(exec_p256_chksign, _1, true))->require_version(4)) diff --git a/crypto/vm/vm.h b/crypto/vm/vm.h index 7afa355c3..cf5322938 100644 --- a/crypto/vm/vm.h +++ b/crypto/vm/vm.h @@ -127,6 +127,7 @@ class VmState final : public VmStateInterface { rist255_validate_gas_price = 200, ecrecover_gas_price = 1500, + secp256k1_xonly_pubkey_tweak_add_gas_price = 1250, chksgn_free_count = 10, chksgn_gas_price = 4000, p256_chksgn_gas_price = 3500, diff --git a/doc/GlobalVersions.md b/doc/GlobalVersions.md index 36d1ab360..7de982ffc 100644 --- a/doc/GlobalVersions.md +++ b/doc/GlobalVersions.md @@ -113,6 +113,13 @@ Operations for working with Merkle proofs, where cells can have non-zero level a ## Version 9 +### New TVM instructions +- `SECP256K1_XONLY_PUBKEY_TWEAK_ADD` (`key tweak - 0 or h x1 x2 -1`) - performs [`secp256k1_xonly_pubkey_tweak_add`](https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1_extrakeys.h#L120). +`key` and `tweak` are 256-bit unsigned integers. 65-byte public key is returned as `uint8 h`, `uint256 x1, x2` (as in `ECRECOVER`). Gas cost: `1276`. +- `mask SETCONTCTRMANY` (`cont - cont'`) - takes continuation, performs the equivalent of `c[i] PUSHCTR SWAP c[i] SETCONTCNR` for each `i` that is set in `mask` (mask is in `0..255`). +- `SETCONTCTRMANYX` (`cont mask - cont'`) - same as `SETCONTCTRMANY`, but takes `mask` from stack. + +### Other changes - Fix `RAWRESERVE` action with flag `4` (use original balance of the account) by explicitly setting `original_balance` to `balance - msg_balance_remaining`. - Previously it did not work if storage fee was greater than the original balance. - Jumps to nested continuations of depth more than 8 consume 1 gas for eact subsequent continuation (this does not affect most of TVM code). \ No newline at end of file