Skip to content

Commit

Permalink
Handle costs varying by size of input
Browse files Browse the repository at this point in the history
  • Loading branch information
jannotti committed May 4, 2023
1 parent 2c1c127 commit 36463e3
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 85 deletions.
2 changes: 1 addition & 1 deletion cmd/opdoc/opdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/algorand/go-algorand/protocol"
)

var docVersion = 9
var docVersion = 10

func opGroupMarkdownTable(names []string, out io.Writer) {
fmt.Fprint(out, `| Opcode | Description |
Expand Down
6 changes: 6 additions & 0 deletions data/transactions/logic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ return stack matches the name of the input value.
| `ecdsa_pk_recover v` | for (data A, recovery id B, signature C, D) recover a public key |
| `ecdsa_pk_decompress v` | decompress pubkey A into components X, Y |
| `vrf_verify s` | Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. |
| `ec_add g` | for curve points A and B, return the curve point A + B |
| `ec_scalar_mul g` | for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B. |
| `ec_pairing_check g` | 1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0 |
| `ec_multi_exp g` | for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + ... + BnAn |
| `ec_subgroup_check g` | 1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all. |
| `ec_map_to g` | maps field element A to group G |
| `+` | A plus B. Fail on overflow. |
| `-` | A minus B. Fail if B > A. |
| `/` | A divided by B (truncated division). Fail if B == 0. |
Expand Down
83 changes: 81 additions & 2 deletions data/transactions/logic/TEAL_opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte
- Opcode: 0x05 {uint8 curve index}
- Stack: ..., A: []byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., uint64
- for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1}
- **Cost**: Secp256k1=1700 Secp256r1=2500
- **Cost**: Secp256k1=1700; Secp256r1=2500
- Availability: v5

`ECDSA` Curves:
Expand All @@ -68,7 +68,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced
- Opcode: 0x06 {uint8 curve index}
- Stack: ..., A: []byte → ..., X: []byte, Y: []byte
- decompress pubkey A into components X, Y
- **Cost**: Secp256k1=650 Secp256r1=2400
- **Cost**: Secp256k1=650; Secp256r1=2400
- Availability: v5

The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded.
Expand Down Expand Up @@ -1560,3 +1560,82 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo
| 0 | BlkSeed | []byte | |
| 1 | BlkTimestamp | uint64 | |


## ec_add g

- Opcode: 0xe0 {uint8 curve}
- Stack: ..., A: []byte, B: []byte → ..., []byte
- for curve points A and B, return the curve point A + B
- **Cost**: BN254g1=310; BN254g2=430; BLS12_381g1=540; BLS12_381g2=750
- Availability: v10

`EC` Groups:

| Index | Name | Notes |
| - | ------ | --------- |
| 0 | BN254g1 | G1 of the BN254 curve. Points encoded as 32 byte X following by 32 byte Y |
| 1 | BN254g2 | G2 of the BN254 curve. Points encoded as 64 byte X following by 64 byte Y |
| 2 | BLS12_381g1 | G1 of the BLS 12-381 curve. Points encoded as 48 byte X following by 48 byte Y |
| 3 | BLS12_381g2 | G2 of the BLS 12-381 curve. Points encoded as 96 byte X following by 48 byte Y |


A and B are curve points in affine representation: field element X concatenated with field element Y. Field element `Z` is encoded as follows.
For the base field elements (Fp), `Z` is encoded as a big-endian number and must be lower than the field modulus.
For the quadratic field extension (Fp2), `Z` is encoded as the concatenation of the individual encoding of the coefficients. For an Fp2 element of the form `Z = Z0 + Z1 i`, where `i` is a formal quadratic non-residue, the encoding of Z is the concatenation of the encoding of `Z0` and `Z1` in this order. (`Z0` and `Z1` must be less than the field modulus).

The point at infinity is encoded as `(X,Y) = (0,0)`.
Groups G1 and G2 are denoted additively.

Fails if A or B is not in G.
A and/or B are allowed to be the point at infinity.
Does _not_ check if A and B are in the main prime-order subgroup.

## ec_scalar_mul g

- Opcode: 0xe1 {uint8 curve}
- Stack: ..., A: []byte, B: []byte → ..., []byte
- for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B.
- **Cost**: BN254g1=2200; BN254g2=4460; BLS12_381g1=3640; BLS12_381g2=8530
- Availability: v10

A is a curve point encoded and checked as described in `ec_add`. Scalar B is interpreted as a big-endian unsigned integer. Fails if B exceeds 32 bytes.

## ec_pairing_check g

- Opcode: 0xe2 {uint8 curve}
- Stack: ..., A: []byte, B: []byte → ..., uint64
- 1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0
- **Cost**: BN254g1=1 + 180 per 64 bytes of B; BN254g2=1 + 180 per 128 bytes of B; BLS12_381g1=450 + 400 per 96 bytes of B; BLS12_381g2=450 + 400 per 192 bytes of B
- Availability: v10

A and B are concatenated points, encoded and checked as described in `ec_add`. A contains points of the group G, B contains points of the associated group (G2 if G is G1, and vice versa). Fails if A and B have a different number of points, or if any point is not in its described group or outside the main prime-order subgroup - a stronger condition than other opcodes.

## ec_multi_exp g

- Opcode: 0xe3 {uint8 curve}
- Stack: ..., A: []byte, B: []byte → ..., []byte
- for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + ... + BnAn
- **Cost**: BN254g1=80 + 3 per 32 bytes of B; BN254g2=180 + 9 per 32 bytes of B; BLS12_381g1=140 + 4 per 32 bytes of B; BLS12_381g2=350 + 18 per 32 bytes of B
- Availability: v10

A is a list of concatenated points, encoded and checked as described in `ec_add`. B is a list of concatenated scalars which, unlike ec_scalar_mul, must all be exactly 32 bytes long.
The name `ec_multi_exp` was chosen to reflect common usage, but a more consistent name would be `ec_multi_scalar_mul`

## ec_subgroup_check g

- Opcode: 0xe4 {uint8 curve}
- Stack: ..., A: []byte → ..., uint64
- 1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all.
- **Cost**: BN254g1=50; BN254g2=11500; BLS12_381g1=5600; BLS12_381g2=7100
- Availability: v10

## ec_map_to g

- Opcode: 0xe5 {uint8 curve}
- Stack: ..., A: []byte → ..., []byte
- maps field element A to group G
- **Cost**: BN254g1=1700; BN254g2=11000; BLS12_381g1=5600; BLS12_381g2=43000
- Availability: v10

BN254 points are mapped by the SVDW map. BLS12-381 points are mapped by the SSWU map.
G1 element inputs are base field elements and G2 element inputs are quadratic field elements, with nearly the same encoding rules (for field elements) as defined in `ec_add`. There is one difference of encoding rule: G1 element inputs do not need to be 0-padded if they fit in less than 32 bytes for BN254 and less than 48 bytes for BLS12-381. (As usual, the empty byte array represents 0.) G2 elements inputs need to be always have the required size.
4 changes: 2 additions & 2 deletions data/transactions/logic/assembler.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ func (ops *OpStream) returns(spec *OpSpec, replacement StackType) {
return
}
}
// returns was called on an OpSpec with no StackAny in its Returns
panic(fmt.Sprintf("%+v", spec))
panic(fmt.Sprintf("returns was called on OpSpec '%s' without StackAny %+v in spec.Return",
spec.Name, spec.Return))
}

// Intc writes opcodes for loading a uint64 constant onto the stack.
Expand Down
2 changes: 1 addition & 1 deletion data/transactions/logic/assembler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ var compiled = map[uint64]string{
8: "08" + v8Compiled,
9: "09" + v9Compiled,

10: "10" + v10Compiled,
10: "0a" + v10Compiled,
}

func pseudoOp(opcode string) bool {
Expand Down
16 changes: 0 additions & 16 deletions data/transactions/logic/evalStateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2966,22 +2966,6 @@ func TestAppInfo(t *testing.T) {
testApp(t, source, ep)
}

func TestBudget(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

source := `
global OpcodeBudget
int 699
==
assert
global OpcodeBudget
int 695
==
`
testApp(t, source, defaultEvalParams())
}

func TestSelfMutateV8(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
Expand Down
80 changes: 51 additions & 29 deletions data/transactions/logic/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,8 @@ func TestGlobal(t *testing.T) {
7: {CallerApplicationAddress, globalV7TestProgram},
8: {CallerApplicationAddress, globalV8TestProgram},
9: {CallerApplicationAddress, globalV9TestProgram},

10: {CallerApplicationAddress, globalV9TestProgram},
}
// tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version
require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1)
Expand Down Expand Up @@ -1674,6 +1676,10 @@ const testTxnProgramTextV9 = testTxnProgramTextV8 + `
assert
int 1
`
const testTxnProgramTextV10 = testTxnProgramTextV9 + `
assert
int 1
`

func makeSampleTxn() transactions.SignedTxn {
var txn transactions.SignedTxn
Expand Down Expand Up @@ -1787,6 +1793,8 @@ func TestTxn(t *testing.T) {
7: testTxnProgramTextV7,
8: testTxnProgramTextV8,
9: testTxnProgramTextV9,

10: testTxnProgramTextV10,
}

for i, txnField := range TxnFieldNames {
Expand Down Expand Up @@ -2959,6 +2967,15 @@ func TestSlowLogic(t *testing.T) {
testLogicBytes(t, ops.Program, ep4, "dynamic cost")
}

func TestOpcodeBudget(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

source := `global OpcodeBudget; int 699; ==; assert
global OpcodeBudget; int 695; ==;`
testApp(t, source, defaultEvalParams())
}

func isNotPanic(t *testing.T, err error) {
if err == nil {
return
Expand Down Expand Up @@ -3126,57 +3143,62 @@ func TestShortBytecblock2(t *testing.T) {

const panicString = "out of memory, buffer overrun, stack overflow, divide by zero, halt and catch fire"

func opPanic(cx *EvalContext) error {
panic(panicString)
}
func checkPanic(cx *EvalContext) error {
panic(panicString)
}

// withPanicOpcode temporarily modifies the opsByOpcode array to include an additional panic opcode.
// This opcode will be named "panic".
// withOpcode temporarily modifies the opsByOpcode array to include an
// additional opcode, specieid by op.
//
// WARNING: do not call this in a parallel test, since it's not safe for concurrent use.
func withPanicOpcode(t *testing.T, version uint64, panicDuringCheck bool, f func(opcode byte)) {
func withOpcode(t *testing.T, version uint64, op OpSpec, f func(opcode byte)) {
t.Helper()
const name = "panic"

var foundEmptySpace bool
var hackedOpcode byte
var oldSpec OpSpec
// Find an unused opcode to temporarily convert to a panicing opcode,
// and append it to program.
// Find an unused opcode to temporarily convert to op
for opcode, spec := range opsByOpcode[version] {
if spec.op == nil {
foundEmptySpace = true
require.LessOrEqual(t, opcode, math.MaxUint8)
hackedOpcode = byte(opcode)
oldSpec = spec

details := detDefault()
if panicDuringCheck {
details.check = checkPanic
}
panicSpec := OpSpec{
Opcode: hackedOpcode,
Name: name,
op: opPanic,
OpDetails: details,
}

opsByOpcode[version][opcode] = panicSpec
OpsByName[version][name] = panicSpec
copy := op
copy.Opcode = hackedOpcode
opsByOpcode[version][opcode] = copy
OpsByName[version][op.Name] = copy
break
}
}
require.True(t, foundEmptySpace, "could not find an empty space for the panic opcode")
require.True(t, foundEmptySpace, "could not find an empty space for the opcode")
defer func() {
opsByOpcode[version][hackedOpcode] = oldSpec
delete(OpsByName[version], name)
delete(OpsByName[version], op.Name)
}()
f(hackedOpcode)
}

// withPanicOpcode temporarily modifies the opsByOpcode array to include an additional panic opcode.
// This opcode will be named "panic".
//
// WARNING: do not call this in a parallel test, since it's not safe for concurrent use.
func withPanicOpcode(t *testing.T, version uint64, panicDuringCheck bool, f func(opcode byte)) {
t.Helper()

opPanic := func(cx *EvalContext) error {
panic(panicString)
}
details := detDefault()
if panicDuringCheck {
details.check = opPanic
}

panicSpec := OpSpec{
Name: "panic",
op: opPanic,
OpDetails: details,
}

withOpcode(t, version, panicSpec, f)
}

func TestPanic(t *testing.T) { //nolint:paralleltest // Uses withPanicOpcode
partitiontest.PartitionTest(t)

Expand Down
85 changes: 84 additions & 1 deletion data/transactions/logic/langspec.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"EvalMaxVersion": 9,
"EvalMaxVersion": 10,
"LogicSigVersion": 8,
"Ops": [
{
Expand Down Expand Up @@ -2702,6 +2702,89 @@
"Groups": [
"State Access"
]
},
{
"Opcode": 224,
"Name": "ec_add",
"Args": "BB",
"Returns": "B",
"Size": 2,
"Doc": "for curve points A and B, return the curve point A + B",
"DocExtra": "A and B are curve points in affine representation: field element X concatenated with field element Y. Field element `Z` is encoded as follows.\nFor the base field elements (Fp), `Z` is encoded as a big-endian number and must be lower than the field modulus.\nFor the quadratic field extension (Fp2), `Z` is encoded as the concatenation of the individual encoding of the coefficients. For an Fp2 element of the form `Z = Z0 + Z1 i`, where `i` is a formal quadratic non-residue, the encoding of Z is the concatenation of the encoding of `Z0` and `Z1` in this order. (`Z0` and `Z1` must be less than the field modulus).\n\nThe point at infinity is encoded as `(X,Y) = (0,0)`.\nGroups G1 and G2 are denoted additively.\n\nFails if A or B is not in G.\nA and/or B are allowed to be the point at infinity.\nDoes _not_ check if A and B are in the main prime-order subgroup.",
"ImmediateNote": "{uint8 curve}",
"IntroducedVersion": 10,
"Groups": [
"Arithmetic"
]
},
{
"Opcode": 225,
"Name": "ec_scalar_mul",
"Args": "BB",
"Returns": "B",
"Size": 2,
"Doc": "for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B.",
"DocExtra": "A is a curve point encoded and checked as described in `ec_add`. Scalar B is interpreted as a big-endian unsigned integer. Fails if B exceeds 32 bytes.",
"ImmediateNote": "{uint8 curve}",
"IntroducedVersion": 10,
"Groups": [
"Arithmetic"
]
},
{
"Opcode": 226,
"Name": "ec_pairing_check",
"Args": "BB",
"Returns": "U",
"Size": 2,
"Doc": "1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0",
"DocExtra": "A and B are concatenated points, encoded and checked as described in `ec_add`. A contains points of the group G, B contains points of the associated group (G2 if G is G1, and vice versa). Fails if A and B have a different number of points, or if any point is not in its described group or outside the main prime-order subgroup - a stronger condition than other opcodes.",
"ImmediateNote": "{uint8 curve}",
"IntroducedVersion": 10,
"Groups": [
"Arithmetic"
]
},
{
"Opcode": 227,
"Name": "ec_multi_exp",
"Args": "BB",
"Returns": "B",
"Size": 2,
"Doc": "for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + ... + BnAn",
"DocExtra": "A is a list of concatenated points, encoded and checked as described in `ec_add`. B is a list of concatenated scalars which, unlike ec_scalar_mul, must all be exactly 32 bytes long.\nThe name `ec_multi_exp` was chosen to reflect common usage, but a more consistent name would be `ec_multi_scalar_mul`",
"ImmediateNote": "{uint8 curve}",
"IntroducedVersion": 10,
"Groups": [
"Arithmetic"
]
},
{
"Opcode": 228,
"Name": "ec_subgroup_check",
"Args": "B",
"Returns": "U",
"Size": 2,
"Doc": "1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all.",
"ImmediateNote": "{uint8 curve}",
"IntroducedVersion": 10,
"Groups": [
"Arithmetic"
]
},
{
"Opcode": 229,
"Name": "ec_map_to",
"Args": "B",
"Returns": "B",
"Size": 2,
"Doc": "maps field element A to group G",
"DocExtra": "BN254 points are mapped by the SVDW map. BLS12-381 points are mapped by the SSWU map.\nG1 element inputs are base field elements and G2 element inputs are quadratic field elements, with nearly the same encoding rules (for field elements) as defined in `ec_add`. There is one difference of encoding rule: G1 element inputs do not need to be 0-padded if they fit in less than 32 bytes for BN254 and less than 48 bytes for BLS12-381. (As usual, the empty byte array represents 0.) G2 elements inputs need to be always have the required size.",
"ImmediateNote": "{uint8 curve}",
"IntroducedVersion": 10,
"Groups": [
"Arithmetic"
]
}
]
}
Loading

0 comments on commit 36463e3

Please sign in to comment.