156 lines
5.9 KiB
Python
156 lines
5.9 KiB
Python
#!/usr/bin/python3
|
|
"""
|
|
================================================================================================
|
|
|
|
The VAA Signature Verify Stateless Program
|
|
|
|
Copyright 2022 Wormhole Project Contributors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
------------------------------------------------------------------------------------------------
|
|
|
|
This program verifies a subset of the signatures in a VAA against the guardian set. This
|
|
program works in tandem with the VAA Processor stateful program.
|
|
|
|
================================================================================================
|
|
|
|
"""
|
|
from pyteal.ast import *
|
|
from pyteal.types import *
|
|
from pyteal.compiler import *
|
|
from pyteal.ir import *
|
|
from globals import *
|
|
from inlineasm import *
|
|
|
|
import sys
|
|
|
|
SLOTID_RECOVERED_PK_X = 240
|
|
SLOTID_RECOVERED_PK_Y = 241
|
|
|
|
|
|
@Subroutine(TealType.uint64)
|
|
def sig_check(signatures, digest, keys, vaa_signature_count):
|
|
si = ScratchVar(TealType.uint64) # signature index (zero-based)
|
|
ki = ScratchVar(TealType.uint64) # key index
|
|
gi = ScratchVar(TealType.uint64) # guardian index (signature prefix)
|
|
i = ScratchVar(TealType.uint64)
|
|
rec_pk_x = ScratchVar(TealType.bytes, SLOTID_RECOVERED_PK_X)
|
|
rec_pk_y = ScratchVar(TealType.bytes, SLOTID_RECOVERED_PK_Y)
|
|
|
|
return Seq(
|
|
[
|
|
rec_pk_x.store(Bytes("")),
|
|
rec_pk_y.store(Bytes("")),
|
|
gi.store(Int(0)),
|
|
For(Seq([
|
|
i.store(Int(0)),
|
|
si.store(Int(0)),
|
|
ki.store(Int(0))
|
|
]),
|
|
si.load() < Len(signatures),
|
|
Seq([
|
|
si.store(si.load() + Int(66)),
|
|
ki.store(ki.load() + Int(20)),
|
|
i.store(i.load() + Int(1)),
|
|
])).Do(
|
|
Seq([
|
|
gi.store(Btoi(Extract(signatures, si.load(), Int(1)))),
|
|
|
|
# Bail out case if we must ignore all signatures past sig_count > 2/3+1
|
|
If (gi.load() >= vaa_signature_count).Then(Break()),
|
|
|
|
# Index must be sequential
|
|
|
|
Assert(gi.load() ==
|
|
i.load() + (Txn.group_index() * Int(MAX_SIGNATURES_PER_VERIFICATION_STEP))),
|
|
|
|
InlineAssembly(
|
|
"ecdsa_pk_recover Secp256k1",
|
|
Keccak256(Keccak256(digest)),
|
|
Btoi(Extract(signatures, si.load() + Int(65), Int(1))),
|
|
Extract(signatures, si.load() + Int(1), Int(32)), # R
|
|
Extract(signatures, si.load() + Int(33), Int(32)), # S
|
|
type=TealType.none),
|
|
|
|
# returned values in stack, pass to scratch-vars
|
|
|
|
InlineAssembly("store " + str(SLOTID_RECOVERED_PK_Y)),
|
|
InlineAssembly("store " + str(SLOTID_RECOVERED_PK_X)),
|
|
|
|
# Generate Ethereum-type public key, compare with guardian key.
|
|
|
|
Assert(
|
|
Extract(keys, ki.load(), Int(20)) ==
|
|
Substring(Keccak256(Concat(rec_pk_x.load(),
|
|
rec_pk_y.load())), Int(12), Int(32))
|
|
)
|
|
])
|
|
|
|
|
|
),
|
|
Return(Int(1))
|
|
]
|
|
)
|
|
|
|
|
|
"""
|
|
* Let N be the number of signatures per verification step, for the TX(i) in group, we verify signatures [j..k] where j = i*N, k = j+(N-1)
|
|
* Input 0 is signatures [j..k] to verify as LogicSigArgs. (Format is GuardianIndex + signature)
|
|
* Input 1 is the total signature count specified in the VAA. This must be > 2/3 of the guardian set plus 1.
|
|
* Input 2 is signed digest of payload, contained in the note field of the TX in current slot.
|
|
* Input 3 is public keys for guardians [j..k] contained in the first Argument of the TX in current slot.
|
|
* Input 4 is guardian set size contained in the second argument of the TX in current slot.
|
|
"""
|
|
|
|
|
|
def vaa_verify_program(vaa_processor_app_id):
|
|
signatures = Arg(0)
|
|
vaa_signature_count = Arg(1)
|
|
digest = Txn.note()
|
|
keys = Txn.application_args[1]
|
|
num_guardians = Txn.application_args[2]
|
|
|
|
return Seq([
|
|
Assert(Txn.fee() <= Int(1000)),
|
|
Assert(Txn.application_args.length() == Int(3)),
|
|
Assert(Btoi(vaa_signature_count) > ((Btoi(num_guardians) * Int(10) / Int(3)) * Int(2)) / Int(10) + Int(1)),
|
|
Assert(Len(signatures) == get_sig_count_in_step(Txn.group_index(), Btoi(num_guardians)) * Int(66)),
|
|
Assert(Txn.rekey_to() == Global.zero_address()),
|
|
Assert(Txn.application_id() == Int(vaa_processor_app_id)),
|
|
Assert(Txn.type_enum() == TxnType.ApplicationCall),
|
|
Assert(Global.group_size() == Int(1) + get_group_size(Btoi(num_guardians))),
|
|
Assert(sig_check(signatures, digest, keys, Btoi(vaa_signature_count))),
|
|
Approve()]
|
|
)
|
|
|
|
if __name__ == "__main__":
|
|
outfile = "teal/wormhole/build/vaa-verify.teal"
|
|
appid = 0
|
|
|
|
print("VAA Verify Stateless Program, (c) 2022 Wormhole Project Contributors")
|
|
print("Compiling...")
|
|
|
|
if len(sys.argv) >= 1:
|
|
appid = sys.argv[1]
|
|
|
|
if len(sys.argv) >= 2:
|
|
outfile = sys.argv[2]
|
|
|
|
with open(outfile, "w") as f:
|
|
compiled = compileTeal(vaa_verify_program(
|
|
int(appid)), mode=Mode.Signature, version=5)
|
|
f.write(compiled)
|
|
|
|
print("Written to " + outfile)
|