#If you have input bytes and output bytes from randomness beacon how would you verify off chain?
96 messages · Page 1 of 1 (latest)
I believe I’ll need to import some library and also get the proof somehow, python preferred but any language is fine
https://github.com/algorand/vrf (Tried a bunch with this but its in Python 2)
giorgio halp
Maybe if anyone is familiar with this?:
def verify_proof(
self,
input_bytes: Bytes,
proof: Bytes,
public_key: Bytes
) -> tuple[Bytes, bool]:
ensure_budget(required_budget=6000, fee_source=OpUpFeeSource.GroupCredit)
random_bytes, correct = op.vrf_verify(op.VrfVerify.VrfAlgorand, input_bytes, proof, public_key)
return random_bytes, correct```
```app_client.compose_call(
atc,
call_abi_method='verify_proof',
input_bytes=bytearray([216, 189, 26, 61, 137, 191, 5, 153, 133, 167, 223, 254, 84, 254, 108, 37, 145, 245, 191, 151]),
proof=b'\x00\x00\x00\x00\x02\xe3\xff\xb8\x00\x00\x00\x00\x02\xe3\xf9\xd8\xc7\x8d\xb5Xs\x86\x81\xdcx>\xa0\\z\x15\xfc\x0c\x1f\nbS\xe2\xbb\xbfB@\x9b~\xdc\xf2(9\x17\xf4\xae\x8e\xbb\xbc\xba\xae/\xf9>\x9b_\xcfG\xd1mpl\x02\tp\x18\xae\x1bC/\x86\x1f \xaaa\x18',
public_key=decode_address('ELJEB3OYX325FATYL765AM5ZSJPSWZX745TYM5KCLTTSHJN2BJSHEMQ2JE'),
)```
```algosdk.error.AlgodHTTPError: TransactionPool.Remember: transaction A5MN3P6YG4BQJJB23ITC5FGKFWKASOJDOK3YAFY2CVHM4KB3HAZQ: logic eval error: proto was executed without a callsub. Details: app=733564386, pc=285, opcodes=uncover 3; retsub; label5:; proto 3 2```
"subroutine": "vrf.get_onchain_randomness.verify_proof",
"params": {
"input_bytes#0": "bytes",
"proof#0": "bytes",
"public_key#0": "bytes"
},
"block": "verify_proof",
"stack_in": [],
"op": "proto 3 2"
},```
This is how I get the proof for the VRF round:
global_states = algod_client.application_info(vrf_app_id)['params']['global-state']
for state in global_states:
if state['key'] == '':
vrf_proof = b64decode(state['value']['bytes'])
print(vrf_proof)```
Its the only global state in the randomness beacon that is 80 bytes long, which the docs say is how long the proof is
def get_random_bytes(
self,
round: UInt64,
input_data: Bytes,
block_seed: String
) -> tuple[UInt64, Bytes, String, DynamicBytes]:
result, txn = abi_call[DynamicBytes](
'must_get(uint64,byte[])byte[]',
round,
input_data,
app_id=self.vrf_app_id,
)
return round, input_data, block_seed, result.copy()```
I'm saving the block seed for the VRF round in the abi results by passing it in if needed, that way its always accessible in the logs
As I noticed in the TEAL for randomness beacon it may use sha512_256 on the input_bytes + block seed concatenated? Not sure if I need that, but stuck on PC code 285 for now
Hello there, I'm not sure I understand what you are trying to do. Why are you trying to verify it off-chain?
The proto error outside of a callsub looks extra strange. Is this compiled algorand python with no modifications? What version of the stack are you on?
On the RB, the global state with an empty key (what the manual calls the main slot) does not contain a proof. It contains the concatenation of two 8-bytes numbers and a 64-byte VRF public key.
All other global state slots contain a VRF output (which is not the proof). The proof is never actually saved onto the state because once the output is verified there's no use for it anymore
Every VRF output saved onto state is already verified so there's no need to save the proof
If you want to get the proof and re-verify it yourself you need to get it from an application call done to the RB, like this one: https://lora.algokit.io/mainnet/transaction/EM5G3JPJPNUOLULXOF37K6ZRXATZP7JPD74TD6F6YSNRB2WAJ6LA
That txn corresponds to this method call: https://github.com/algorandfoundation/randomness-beacon/blob/main/pyteal/main.py#L445
@delicate marsh this method looks like it may only work until the VRF round expires?
The submit method
Trying to verify off-chain was something I got into when I was getting the proto error
def sha512_256(data: bytes) -> str:
hash_obj = hashlib.new("sha512_256")
hash_obj.update(data)
return hash_obj.digest()
def sha3_256(data: bytes) -> str:
hash_obj = hashlib.sha3_256()
hash_obj.update(data)
return hash_obj.hexdigest()
test_round_bytes = b64decode('AAAAAALl7jA=')
test_proof = b64decode('r5Pk400IrCVoAc1CvY+Nm84JfBiysafYK4EMoI40xQ00Giv+vkFFNWYb8sdVMyXQhpslT6jGDV6Dbs3Ons4+zk6JDwZHMMCPoOoWr37hQQA=')
block_seed_bytes = b64decode(algod_client.block_info(int.from_bytes(test_round_bytes, 'big'))['block']['seed'])
global_states = algod_client.application_info(vrf_app_id)['params']['global-state']
for state in global_states:
if state['key'] == '':
public_key = b64decode(state['value']['bytes'])[16:48]
data = test_round_bytes + block_seed_bytes + test_proof + public_key
output = sha512_256(data)[0:32]
user_data = bytearray([171, 251, 35, 149, 52, 254, 178, 9, 195, 21, 237, 204, 24, 41, 127, 64, 40, 22, 212, 128])
result = list(b64decode(sha3_256(output + test_round_bytes + user_data)))
print(result)```
Which is producing an incorrect result atm
I'm using AVM version 10
I got the proof by following the submit app calls for the VRF round-- the output was
[215, 78, 28, 123, 93, 223, 111, 199, 30, 219, 150, 188, 231, 94, 118, 121, 174, 184, 243, 173, 28, 221, 231, 218, 109, 182, 219, 227, 102, 156, 113, 231, 189, 235, 167, 154, 217, 199, 124, 105, 189, 252, 107, 158, 253, 239, 182, 220]
When it should have been:
[176, 62, 103, 55, 140, 18, 142, 35, 205, 208, 105, 25, 40, 240, 210, 129, 184, 246, 235, 24, 85, 160, 78, 247, 135, 72, 42, 187, 8, 110, 209, 127]
Correct
Well no I believe the output is truncated somewhere
Just because the randomness beacon doesn't need that much enthropy
Hold on a sec
Actually now that I think about it, if you want to verify something off-chain you could always run a read-only app call (which is done by using simulate)
That way you don't have to mess about with different PK/proof/output representations and weird C wrappers for Python
Also, what is this?
data = test_round_bytes + block_seed_bytes + test_proof + public_key
output = sha512_256(data)[0:32]
I was trying to reproduce the output_bytes
from the randomness beacon
Using the proof, input bytes, and block seed for that VRF round
If you're sure the method that is read-only doesn't expire that could work, or I could even replicate the logic if I had a reference
Do you remember what the method was called?
Also Gm ☕
I see. Although it's technically possible to compute the output starting from the proof only (without the secret key), that's not the way to do it. If you feel inclined to do it yourself (I'd strongly recommend against it), this is the procedure to do it https://www.rfc-editor.org/rfc/rfc9381.html#name-ecvrf-proof-to-hash
The submit method on the Randomness Beacon is not read-only. It changes the state of the app by saving the truncated output therefore it can't be read-only
All read-only methods are called with simulate but the inverse is not true. You can still call a non read-only method with simulate and fake arguments. You can even the signature step on a simulate app call
If you want to verify a VRF proof off-chain, I would just write a smart contract that does what you want and then only call it with a simulate call on a node you trust is running the original AVM
This is what the vrf_verify opcode does https://developer.algorand.org/docs/get-details/dapps/avm/teal/opcodes/v11/#vrf_verify
Ahh I see
If you want to write the verification algorithm yourself, you should at least do it in golang using gnark because otherwise you are in a world of EC point representation pain
Nono lol, but does the vrf_verify opcode only work with simulate then?
I tried it here
But didn’t simulate
Calling like so with the error included here
Sorry I sent you a bunch of stuff at the time lol
That's why I asked what did you use to compile it because that error about proto outside of callsub is sus
algokit compile py filename.py
algokit -v? That's not it. I meant to ask which version of puyapy you are on
Hmmm…. I’ll have to check in a moment
If I’m remembering correctly it did autocomplete which may indicate a sufficient version for puyapy but I’ll double check shortly
AlgoKit: 2.5.2
AlgoKit Python: 3.13.1 (tags/v3.13.1:0671451, Dec 3 2024, 19:06:28) [MSC v.1942 64 bit (AMD64)] (location: C:\Users\atsoc\AppData\Local\Programs\Python\Python313)
OS: Windows-11-10.0.26100-SP0
docker: 27.4.0
docker compose: 2.31.0-desktop.2
git: 2.47.1.windows.2
python: 3.13.1 (location: C:\Users\atsoc\AppData\Local\Programs\Python\Python313\python.EXE)
python3: Command not found!
pipx: 1.7.1
poetry: 2.0.1
node: 22.13.1
npm: 10.9.2
winget: 1.9.25200```
Ok. I doubt that's the issue so in the meantime I'll also point out that the VRF public key is saved in the main slot of the randomness beacon concat how I linked before (I doubt that it's what you did public_key=decode_address('ELJEB3OYX325FATYL765AM5ZSJPSWZX745TYM5KCLTTSHJN2BJSHEMQ2JE')
The proof you want to extract it from a real transaction to the randomness beacon
for state in global_states:
if state['key'] == '':
public_key = b64decode(state['value']['bytes'])[16:48]
```
Output:
```Y6G3KWDTQ2A5Y6B6UBOHUFP4BQPQUYST4K536QSATN7NZ4RIHELZUT5PRE```
Ahh yes but I didnt use this public key in my method initially
algosdk.error.AlgodHTTPError: TransactionPool.Remember: transaction ZYJ3567QZLJOWOK6LNLHUZBQSCOLGRQ7PK62QR26SLGJL5MFYVUA: logic eval error: proto was executed without a callsub. Details: app=733564386, pc=285, opcodes=uncover 3; retsub; label5:; proto 3 2
atc,
call_abi_method='verify_proof',
input_bytes=bytearray([216, 189, 26, 61, 137, 191, 5, 153, 133, 167, 223, 254, 84, 254, 108, 37, 145, 245, 191, 151]),
proof=b'\x00\x00\x00\x00\x02\xe3\xff\xb8\x00\x00\x00\x00\x02\xe3\xf9\xd8\xc7\x8d\xb5Xs\x86\x81\xdcx>\xa0\\z\x15\xfc\x0c\x1f\nbS\xe2\xbb\xbfB@\x9b~\xdc\xf2(9\x17\xf4\xae\x8e\xbb\xbc\xba\xae/\xf9>\x9b_\xcfG\xd1mpl\x02\tp\x18\xae\x1bC/\x86\x1f \xaaa\x18',
public_key=decode_address('Y6G3KWDTQ2A5Y6B6UBOHUFP4BQPQUYST4K536QSATN7NZ4RIHELZUT5PRE'),
)
def verify_proof(
self,
input_bytes: Bytes,
proof: Bytes,
public_key: Bytes
) -> tuple[Bytes, bool]:
ensure_budget(required_budget=6000, fee_source=OpUpFeeSource.GroupCredit)
random_bytes, correct = op.vrf_verify(op.VrfVerify.VrfAlgorand, input_bytes, proof, public_key)
return random_bytes, correct```
The proof I got from the latest transaction before the VRF round, but I'm not sure if that error comes from an incorrect proof
"285": {
"subroutine": "vrf.get_onchain_randomness.verify_proof",
"params": {
"input_bytes#0": "bytes",
"proof#0": "bytes",
"public_key#0": "bytes"
},
"block": "verify_proof",
"stack_in": [],
"op": "proto 3 2"
},```
atc,
call_abi_method='verify_proof',
input_bytes=bytearray([216, 189, 26, 61, 137, 191, 5, 153, 133, 167, 223, 254, 84, 254, 108, 37, 145, 245, 191, 151]),
proof=b'\x00\x00\x00\x00\x02\xe3\xff\xb8\x00\x00\x00\x00\x02\xe3\xf9\xd8\xc7\x8d\xb5Xs\x86\x81\xdcx>\xa0\\z\x15\xfc\x0c\x1f\nbS\xe2\xbb\xbfB@\x9b~\xdc\xf2(9\x17\xf4\xae\x8e\xbb\xbc\xba\xae/\xf9>\x9b_\xcfG\xd1mpl\x02\tp\x18\xae\x1bC/\x86\x1f \xaaa\x18',
public_key=decode_address('Y6G3KWDTQ2A5Y6B6UBOHUFP4BQPQUYST4K536QSATN7NZ4RIHELZUT5PRE'),
)
sim_req = SimulateRequest(txn_groups=atc.build_group())
results = atc.simulate(algod_client, sim_req)
print(results.simulate_response)```
Failure response:
```op\Projects\vrf\3_call_verify.py'
{'last-round': 48714885, 'txn-groups': [{'app-budget-added': 1400, 'app-budget-consumed': 41, 'failed-at': [0], 'failure-message': 'transaction AQ7J4EOSNQJEV44HP5Z7IJSOXU7DZAXQTDDZZPPDLD45NSCI2OGQ: logic eval error: proto was executed without a callsub. Details: app=733564386, pc=285, opcodes=uncover 3; retsub; label5:; proto 3 2',
Same thing when simulating as well
I believe the VRF public key is 64 bytes long
Yeah I just confirmed it because the main slot is 80 bytes long
Hmm
for state in global_states:
if state['key'] == '':
public_key = b64decode(state['value']['bytes'])
print(public_key)
print(len(public_key))```
```b'\x00\x00\x00\x00\x02\xe7U@\x00\x00\x00\x00\x02\xe7O`\xc7\x8d\xb5Xs\x86\x81\xdcx>\xa0\\z\x15\xfc\x0c\x1f\nbS\xe2\xbb\xbfB@\x9b~\xdc\xf2(9\x17\xf4\xae\x8e\xbb\xbc\xba\xae/\xf9>\x9b_\xcfG\xd1mpl\x02\tp\x18\xae\x1bC/\x86\x1f \xaaa\x18'
80```
Yeah
16:80 then?
Sure
Tried with [16:80] and [0:64] for the verify_proof method above, still returning the same error
I think the issue is that you are not increasing the group fees leading to the contract perhaps not making the ensure budget inner calls
It's 16:80. Don't need to try 0:64
Tried submitting with 30,000, same error
Oh, got it working
Oh cool. What was the issue
I removed the vrf_verify op code
def verify_my_proof(
self,
input_bytes: Bytes,
proof: Bytes,
public_key: Bytes
) -> tuple[Bytes, bool]:
ensure_budget(required_budget=6000, fee_source=OpUpFeeSource.GroupCredit)
# random_bytes, correct = op.vrf_verify(op.VrfVerify.VrfAlgorand, input_bytes, proof, public_key)
return input_bytes, False```
Works now
Jk lol
Can you paste the TEAL for both?
So I changed the method from 'verify_proof' to 'verify_my_proof'
def verify_my_proof(
self,
input_bytes: Bytes,
proof: Bytes,
public_key: Bytes
) -> tuple[Bytes, bool]:
ensure_budget(required_budget=6000, fee_source=OpUpFeeSource.GroupCredit)
random_bytes, correct = op.vrf_verify(op.VrfVerify.VrfAlgorand, input_bytes, proof, public_key)
return random_bytes, correct```
Sure
// vrf.py:29
// @abimethod
txn OnCompletion
!
assert // OnCompletion is not NoOp
txn ApplicationID
assert // can only call when not creating
// vrf.py:6
// class get_onchain_randomness(ARC4Contract):
txna ApplicationArgs 1
extract 2 0
// vrf.py:29
// @abimethod
dup
len
itob
extract 6 2
swap
concat
pushbytes 0x000300
swap
concat
bytec_0 // 0x151f7c75
swap
concat
log
intc_0 // 1
return```
Is this the whole file?
Calling for VRF requested for 48716202, where the next VRF round is 48716204
https://lora.algokit.io/testnet/transaction/QCNVQL7UNGXAEZN4VAL2CAXRJRSYQSWBGEESDNXM62WTKMVZRVAQ
with input bytes:
[251, 149, 255, 191, 188, 243, 54, 219, 84, 37, 161, 31, 27, 28, 63, 186, 64, 252, 150, 49]
I check for the proof on LORA and find this TX from randomness beacon host at round 48716204:
https://lora.algokit.io/testnet/transaction/4MD3EI4VFXTJUG2CMED7LRL3O4JKOKKKEYLLOQ5SA5MGHNPZUV3Q
Where the proof arg is the 3rd app arg, and the first arg is 'pJ1KeA==' :
and the public key is bytes [16:80] in global state " "
b'\xc7\x8d\xb5Xs\x86\x81\xdcx>\xa0\\z\x15\xfc\x0c\x1f\nbS\xe2\xbb\xbfB@\x9b~\xdc\xf2(9\x17\xf4\xae\x8e\xbb\xbc\xba\xae/\xf9>\x9b_\xcfG\xd1mpl\x02\tp\x18\xae\x1bC/\x86\x1f \xaaa\x18'```
Using this info on my 'verify_my_proof' method it returns the bytes (input bytes) and False. I also tried the nearest next tx in case there was a delay:
```[[251, 149, 255, 191, 188, 243, 54, 219, 84, 37, 161, 31, 27, 28, 63, 186, 64, 252, 150, 49], False]```
```input_bytes = bytes(bytearray([251, 149, 255, 191, 188, 243, 54, 219, 84, 37, 161, 31, 27, 28, 63, 186, 64, 252, 150, 49]))
proof = b64decode('HygleKYlU9g6TWcN9YSPpiLPPvQ0d/jLbQTtEr6U1TYZ9vuwg+MhQIDNu1uVgYhRU5q2tbruxBC8hVKTCSDN+cRJD6lJ+nBX6aEGux19ngw=')
public_key=b'\xc7\x8d\xb5Xs\x86\x81\xdcx>\xa0\\z\x15\xfc\x0c\x1f\nbS\xe2\xbb\xbfB@\x9b~\xdc\xf2(9\x17\xf4\xae\x8e\xbb\xbc\xba\xae/\xf9>\x9b_\xcfG\xd1mpl\x02\tp\x18\xae\x1bC/\x86\x1f \xaaa\x18'
app_client.compose_call(
atc,
call_abi_method='verify_my_proof',
input_bytes=input_bytes,
proof=proof,
public_key=public_key,
)
results = atc.execute(algod_client, 2)
abi_results = [results.abi_results[i].return_value for i in range(len(results.abi_results))][0]
tx_ids = results.tx_ids[0]
print(abi_results)
print(tx_ids)```
AlgoKit lora, the Algorand live on-chain resource analyzer.
This is the full approval.teal
Ignore block seed if you see it, its not actually doing anything
With regards to the proto 3 2 error, we confirmed offline that it's a compilation optimization issue. With optimization disabled that error shouldn't be there
This bug is now solved in the latest revision of puya @fresh dust
https://github.com/algorandfoundation/puya/releases/tag/v4.4.1
Thank you @delicate marsh !