From 0f1b25e53c2b0f19ce3fa351e6d90db16301c5bc Mon Sep 17 00:00:00 2001 From: step Date: Mon, 16 Sep 2024 18:00:06 +0200 Subject: [PATCH] add new operators to avoid pedersen commitments in amount checks --- src/vm/macroasm.rs | 3 + src/vm/op_contract.rs | 148 +++++++++++++++++++++++++++++++++++++++++- src/vm/opcodes.rs | 5 +- 3 files changed, 152 insertions(+), 4 deletions(-) diff --git a/src/vm/macroasm.rs b/src/vm/macroasm.rs index f05a3d71..bd356133 100644 --- a/src/vm/macroasm.rs +++ b/src/vm/macroasm.rs @@ -36,6 +36,9 @@ macro_rules! isa_instr { (pcvs $no:ident) => {{ RgbIsa::Contract(ContractOp::Pcvs($no)) }}; (pcas $no:ident) => {{ RgbIsa::Contract(ContractOp::Pcas($no)) }}; (pcps $no:ident) => {{ RgbIsa::Contract(ContractOp::Pcps($no)) }}; + (svs $no:ident) => {{ RgbIsa::Contract(ContractOp::Svs($no)) }}; + (sas $no:ident) => {{ RgbIsa::Contract(ContractOp::Sas($no)) }}; + (sps $no:ident) => {{ RgbIsa::Contract(ContractOp::Sps($no)) }}; (cng $t:ident,a8[$a_idx:literal]) => {{ RgbIsa::Contract(ContractOp::CnG($t, Reg32::from(u5::with($a_idx)))) }}; (cnc $t:ident,a16[$a_idx:literal]) => {{ RgbIsa::Contract(ContractOp::CnC($t, Reg32::from(u5::with($a_idx)))) }}; (ldm $t:ident,s16[$s_idx:literal]) => {{ RgbIsa::Contract(ContractOp::LdM($t, RegS::from($s_idx))) }}; diff --git a/src/vm/op_contract.rs b/src/vm/op_contract.rs index 5d971ff9..e815c9ec 100644 --- a/src/vm/op_contract.rs +++ b/src/vm/op_contract.rs @@ -137,6 +137,43 @@ pub enum ContractOp { #[display("ldm {0},{1}")] LdM(MetaType, RegS), + /// Verify sum of inputs and outputs are equal. + /// + /// The only argument specifies owned state type for the sum operation. If + /// this state does not exist, or either inputs or outputs does not have + /// any data for the state, the verification fails. + /// + /// If verification succeeds, doesn't change `st0` value; otherwise sets it + /// to `false` and stops execution. + #[display("svs {0}")] + Svs(AssignmentType), + + /// Verify sum of outputs and value in `a64[0]` register are equal. + /// + /// The first argument specifies owned state type for the sum operation. If + /// this state does not exist, or either inputs or outputs does not have + /// any data for the state, the verification fails. + /// + /// If `a64[0]` register does not contain value, the verification fails. + /// + /// If verification succeeds, doesn't change `st0` value; otherwise sets it + /// to `false` and stops execution. + #[display("sas {0}")] + Sas(/** owned state type */ AssignmentType), + + /// Verify sum of inputs and value in `a64[0]` register are equal. + /// + /// The first argument specifies owned state type for the sum operation. If + /// this state does not exist, or either inputs or outputs does not have + /// any data for the state, the verification fails. + /// + /// If `a64[0]` register does not contain value, the verification fails. + /// + /// If verification succeeds, doesn't change `st0` value; otherwise sets it + /// to `false` and stops execution. + #[display("sps {0}")] + Sps(/** owned state type */ AssignmentType), + /// Verify sum of pedersen commitments from inputs and outputs. /// /// The only argument specifies owned state type for the sum operation. If @@ -202,6 +239,8 @@ impl InstructionSet for ContractOp { | ContractOp::LdM(_, _) => bset![], ContractOp::Pcvs(_) => bset![], ContractOp::Pcas(_) | ContractOp::Pcps(_) => bset![Reg::A(RegA::A64, Reg32::Reg0)], + ContractOp::Svs(_) => bset![], + ContractOp::Sas(_) | ContractOp::Sps(_) => bset![Reg::A(RegA::A64, Reg32::Reg0)], ContractOp::Fail(_, _) => bset![], } } @@ -227,6 +266,9 @@ impl InstructionSet for ContractOp { ContractOp::Pcvs(_) | ContractOp::Pcas(_) | ContractOp::Pcps(_) => { bset![] } + ContractOp::Svs(_) | ContractOp::Sas(_) | ContractOp::Sps(_) => { + bset![] + } ContractOp::Fail(_, _) => bset![], } } @@ -243,6 +285,10 @@ impl InstructionSet for ContractOp { | ContractOp::LdG(_, _, _) | ContractOp::LdC(_, _, _) => 8, ContractOp::LdM(_, _) => 6, + // TODO: what are the proper values for complexity? + ContractOp::Svs(_) + | ContractOp::Sas(_) + | ContractOp::Sps(_) => 20, ContractOp::Pcvs(_) => 1024, ContractOp::Pcas(_) | ContractOp::Pcps(_) => 512, ContractOp::Fail(_, _) => u64::MAX, @@ -286,6 +332,38 @@ impl InstructionSet for ContractOp { } }}; } + macro_rules! load_revealed_inputs { + ($state_type:ident) => {{ + let Some(prev_state) = context.op_info.prev_state.get($state_type) else { + fail!() + }; + match prev_state { + TypedAssigns::Fungible(state) => state + .iter() + .map(Assign::as_revealed_state) + // TODO: properly fail if we can't read revealed state + .map(|s| s.unwrap().value.as_u64()) + .collect::>(), + _ => fail!(), + } + }}; + } + macro_rules! load_revealed_outputs { + ($state_type:ident) => {{ + let Some(new_state) = context.op_info.owned_state.get(*$state_type) else { + fail!() + }; + match new_state { + TypedAssigns::Fungible(state) => state + .iter() + .map(Assign::as_revealed_state) + // TODO: properly fail if we can't read revealed state + .map(|s| s.unwrap().value.as_u64()) + .collect::>(), + _ => fail!(), + } + }}; + } match self { ContractOp::CnP(state_type, reg) => { @@ -473,6 +551,59 @@ impl InstructionSet for ContractOp { fail!() } } + ContractOp::Svs(state_type) => { + let Some(input_amt) = load_revealed_inputs!(state_type) + .iter() + .try_fold(0u64, |acc, &x| acc.checked_add(x)) + else { + fail!() + }; + let Some(output_amt) = load_revealed_outputs!(state_type) + .iter() + .try_fold(0u64, |acc, &x| acc.checked_add(x)) + else { + fail!() + }; + if input_amt != output_amt { + fail!() + } + } + + ContractOp::Sas(owned_state) => { + let Some(sum) = *regs.get_n(RegA::A64, Reg32::Reg0) else { + fail!() + }; + let sum = u64::from(sum); + + let Some(output_amt) = load_revealed_outputs!(owned_state) + .iter() + .try_fold(0u64, |acc, &x| acc.checked_add(x)) + else { + fail!() + }; + + if sum != output_amt { + fail!() + } + } + + ContractOp::Sps(owned_state) => { + let Some(sum) = *regs.get_n(RegA::A64, Reg32::Reg0) else { + fail!() + }; + let sum = u64::from(sum); + + let Some(input_amt) = load_revealed_inputs!(owned_state) + .iter() + .try_fold(0u64, |acc, &x| acc.checked_add(x)) + else { + fail!() + }; + + if sum != input_amt { + fail!() + } + } // All other future unsupported operations, which must set `st0` to `false`. _ => fail!(), } @@ -501,6 +632,10 @@ impl Bytecode for ContractOp { ContractOp::Pcas(_) => INSTR_PCAS, ContractOp::Pcps(_) => INSTR_PCPS, + ContractOp::Svs(_) => INSTR_SVS, + ContractOp::Sas(_) => INSTR_SAS, + ContractOp::Sps(_) => INSTR_SPS, + ContractOp::Fail(other, _) => *other, } } @@ -559,9 +694,12 @@ impl Bytecode for ContractOp { writer.write_u4(u4::ZERO)?; } - ContractOp::Pcvs(state_type) => writer.write_u16(*state_type)?, - ContractOp::Pcas(owned_type) => writer.write_u16(*owned_type)?, - ContractOp::Pcps(owned_type) => writer.write_u16(*owned_type)?, + ContractOp::Pcvs(state_type) + | ContractOp::Svs(state_type) => writer.write_u16(*state_type)?, + ContractOp::Pcas(owned_type) + | ContractOp::Sas(owned_type) => writer.write_u16(*owned_type)?, + ContractOp::Pcps(owned_type) + | ContractOp::Sps(owned_type) => writer.write_u16(*owned_type)?, ContractOp::Fail(_, _) => {} } @@ -630,6 +768,10 @@ impl Bytecode for ContractOp { INSTR_PCAS => Self::Pcas(reader.read_u16()?.into()), INSTR_PCPS => Self::Pcps(reader.read_u16()?.into()), + INSTR_SVS => Self::Svs(reader.read_u16()?.into()), + INSTR_SAS => Self::Sas(reader.read_u16()?.into()), + INSTR_SPS => Self::Sps(reader.read_u16()?.into()), + x => Self::Fail(x, PhantomData), }) } diff --git a/src/vm/opcodes.rs b/src/vm/opcodes.rs index b1db3ea4..64a26493 100644 --- a/src/vm/opcodes.rs +++ b/src/vm/opcodes.rs @@ -47,8 +47,11 @@ pub const INSTR_PCVS: u8 = 0b11_010_000; pub const INSTR_PCAS: u8 = 0b11_010_001; pub const INSTR_PCPS: u8 = 0b11_010_010; // Reserved 0b11_010_011 +pub const INSTR_SVS: u8 = 0b11_010_100; +pub const INSTR_SAS: u8 = 0b11_010_101; +pub const INSTR_SPS: u8 = 0b11_010_110; pub const INSTR_CONTRACT_FROM: u8 = 0b11_000_000; -pub const INSTR_CONTRACT_TO: u8 = 0b11_010_011; +pub const INSTR_CONTRACT_TO: u8 = 0b11_011_011; // TIMECHAIN: pub const INSTR_TIMECHAIN_FROM: u8 = 0b11_011_100;