janezpodhostnik / flow-py-sdk

Unofficial flow blockchain python sdk
MIT License
35 stars 26 forks source link

How can I implement Proof of account with sdk? #47

Open davit555 opened 2 years ago

davit555 commented 2 years ago

It seems every other sdk has such functionality (C#, Go, Node...) and I don't know how to verify proof of account with the python SDK ...

In particular when js client sends the authentication with .put("fcl.accountProof.resolver", resolver) adding to the config

where the resolver is something like this

const resolver = async () => { return { appIdentifier: "MyAppIdentifier", nonce: "75f8587e5bd5f9dcc9909d0dae1f0ac5814458b2ae129620502cb936fde7120a" }

then it gets some signature

{ "f_type": "Service", "f_vsn": "1.0.0", "type": "account-proof", "method": "DATA", "uid": "blocto#account-proof", "data": { "f_type": "account-proof", "f_vsn": "2.0.0", "signatures": [ { "f_type": "CompositeSignature", "f_vsn": "1.0.0", "addr": "0x46dfa5325a7bb983", "keyId": 1, "signature": "ce972efe49abc05a3bc8d71ff1e48e42d8d869a3ca30efb5e39ebbdb3f8a37fffb6f3bddd8eeacf803755c2726b64a9049be8360e9d2889ec914a301ab73da3d" } ], "address": "0x46dfa5325a7bb983", "timestamp": null, "nonce": "75f8587e5bd5f9dcc9909d0dae1f0ac5814458b2ae129620502cb936fde7120a" } }

then I need to somehow verify this signature from maybe verify_user_signature

method but I don't know what to pass as message parameter...

do I need to encode the message like rlp or perform some other serialization....

Please help to figure out this

gurd33pS1ngh commented 1 year ago

I need same functionality . Do you able to solve it? @davit555

davit555 commented 1 year ago

yes I have manually implemented it... but it is too strange that it didn't exist in sdk.... @gurd33pS1ngh

davit555 commented 1 year ago
from flow_py_sdk.exceptions import PySDKError
from flow_py_sdk.client import AccessAPI
from flow_py_sdk.script import Script
from flow_py_sdk.utils import CompositeSignature
from rlp import encode

async def verify(app_identifier, account_address, nonce, blockchain_network, signature, key_id, env, port=9000):
    async with flow_client(
            host=blockchain_network, port=port
    ) as client:
        account = await client.get_account(
            address=account_address.to_bytes((account_address.bit_length() + 7) // 8, "big")
        )

        fields = encode(
            [
                app_identifier,  # String
                account.address,  # Byte
                bytes.fromhex(nonce)  # Byte from hex number
            ]
        )

        signer = CompositeSignature(
            addr=account.address.hex(),
            keyId=key_id,
            signature=signature
        )
        is_verified = await verify_account_proof_signatures(
            client=client, message=bytes(fields.hex(), "utf-8"), composite_signatures=[signer], env=env
        )
        return is_verified

async def verify_account_proof_signatures(*, client: AccessAPI, message: bytes,
                                          composite_signatures: list[CompositeSignature], env: str
                                          ) -> bool:
    # if there is no signature return False
    if len(composite_signatures) == 0:
        return False

    # it does not make sense for the signatures to be from different addresses
    if any(x.addr != composite_signatures[0].addr for x in composite_signatures):
        raise PySDKError("All signatures must be from the same address")

    address = cadence.Address.from_hex(composite_signatures[0].addr)
    signatures = cadence.Array(
        [cadence.String(x.signature) for x in composite_signatures]
    )
    key_indexes = cadence.Array([cadence.Int(x.keyId) for x in composite_signatures])
    cadence_message = cadence.String(str(message, "utf-8"))

    if env == "Prod":
        address_of_network = "0xdb6b70764af4ff68"
    else:
        address_of_network = "0x5b250a8a85b44a67"

    script = Script(
        code=
        f'''        
        import FCLCrypto from {address_of_network}
        pub fun main(address: Address,
                     message: String,
                     keyIndices: [Int],
                     signatures: [String]): Bool''' + '''{
            return FCLCrypto.verifyAccountProofSignatures(address: address, message: message, keyIndices: keyIndices, 
            signatures: signatures)
         }   
        ''',
        arguments=[
            address,
            cadence_message,
            key_indexes,
            signatures,
        ],
    )
    script_result = await client.execute_script(script)

    if script_result is None:
        return False

    return script_result.as_type(cadence.Bool).value

for anyone that needs that missing code part...