Skip to content

Commit

Permalink
feat: AENS delegation signatures helpers (#353)
Browse files Browse the repository at this point in the history
* add delegation signatures helpers (WIP)
* add tests
* add docs
* auto string to bytes conversion for ae entities (cm_..., ct_..., etc)

NOTE: tests are disable pending node fix + hf aeternity/aeternity#3141
  • Loading branch information
noandrea committed Jan 31, 2020
1 parent f9a7792 commit e11d441
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 12 deletions.
5 changes: 3 additions & 2 deletions aeternity/contract_native.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from aeternity import utils, defaults, identifiers, signing, compiler
from aeternity import utils, defaults, identifiers, signing, compiler, hashing
from aeternity.contract import Contract

from munch import Munch
Expand Down Expand Up @@ -283,7 +283,8 @@ def from_sophia_hash(self, arg, generic, bindings={}):

def to_sophia_bytes(self, arg, generic, bindings={}):
if isinstance(arg, str):
return f'#{arg}'
val = hashing.decode(arg).hex() if utils.is_valid_hash(arg) else arg
return f'#{val}'
elif isinstance(arg, bytes):
return f"#{arg.hex()}"

Expand Down
68 changes: 68 additions & 0 deletions aeternity/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,74 @@ def account_basic_to_ga(self, account: Account, ga_contract: str, calldata: str,
self.broadcast_transaction(tx)
return tx

def delegate_name_preclaim_signature(self, account: Account, contract_id: str):
"""
Helper to generate a signature to delegate a name preclaim to a contract.
Args:
account: the account authorizing the transaction
contract_id: the if of the contract executing the transaction
Returns:
the signature to use for delegation
"""
return self._delegate_common(account, contract_id)

def delegate_name_claim_signature(self, account: Account, contract_id: str, name: str):
"""
Helper to generate a signature to delegate a name claim to a contract.
Args:
account: the account authorizing the transaction
contract_id: the if of the contract executing the transaction
name: the name being claimed
Returns:
the signature to use for delegation
"""
return self._delegate_common(account, hashing.name_id(name), contract_id)

def delegate_name_transfer_signature(self, account: Account, contract_id: str, name: str):
"""
Helper to generate a signature to delegate a name tranfer to a contract.
Args:
account: the account authorizing the transaction
contract_id: the if of the contract executing the transaction
name: the name being transferred
Returns:
the signature to use for delegation
"""
return self._delegate_common(account, hashing.name_id(name), contract_id)

def delegate_name_revoke_signature(self, account: Account, contract_id: str, name: str):
"""
Helper to generate a signature to delegate a name revoke to a contract.
Args:
account: the account authorizing the transaction
contract_id: the if of the contract executing the transaction
name: the name being revoked
Returns:
the signature to use for delegation
"""
return self._delegate_common(account, hashing.name_id(name), contract_id)

def _delegate_common(self, account: Account, *kwargs):
"""
Utility method to create a delegate signature for a contract
Args:
account: the account authorizing the transaction
kwargs: the list of additional entity ids to be added to the data to be signed
Returns:
the signature to use for delegation
"""
sig_data = self.config.network_id.encode("utf8") + hashing.decode(account.get_address())
for _id in kwargs:
sig_data += hashing.decode(_id)
# sign the data
sig = account.sign(sig_data)
return sig

# support naming
def AEName(self, domain):
return aens.AEName(domain, client=self)
Expand Down
1 change: 1 addition & 0 deletions docs/howto/accounts_signatures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Create an account (non HD)
Generate a new account

::

# import the Account class from signing
from aeternity.signing import Account

Expand Down
46 changes: 46 additions & 0 deletions docs/howto/delegate_signatures.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
=================================
Transaction delegation signatures
=================================

The `Sophia`_ language for smart contracts allow to delegate
the transaction execution to a contract by providing
delegation signatures.

.. _Sophia: https://github.com/aeternity/protocol/blob/aeternity-node-v5.4.1/contracts/sophia.md

Delegate signatures for AENS
============================

The following code snippet shows how to generate
signatures for name transactions delegation to a contract

::

# import the required libraries
from aeternity.node import NodeClient, Config
from aeternity.signing import Account

# initialize the node client
node_cli = NodeClient(Config(external_url="https://mainnet.aeternal.io"))
# get an account
account = Account.from_keystore("/path/to/keystore", "keystore password")

# an example name
name = "example.chain"
# name preclaim signature delegation
sig = node_cli.delegate_name_preclaim_signature(account, contract_id)
# name claim signature delegation
sig = node_cli.delegate_name_claim_signature(account, contract_id, name)
# name revoke signature delegation
sig = node_cli.delegate_name_revoke_signature(account, contract_id, name)
# name revoke signature delegation
sig = node_cli.delegate_name_transfer_signature(account, contract_id, name)


1 change: 1 addition & 0 deletions docs/howto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ you quickly accomplish common tasks.
cli_accounts
validate_contract_bytecode
amounts
delegate_signatures

.. seealso::

Expand Down
4 changes: 0 additions & 4 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
from aeternity import openapi
import semver

from pytest import skip

# from aeternity.exceptions import TransactionNotFoundException


def test_api_get_account(chain_fixture):

Expand Down
6 changes: 0 additions & 6 deletions tests/test_contract_lima.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import pytest
from pytest import skip

#
# SOPHIA
#


def _sophia_contract_tx_create_online(node_cli, account):
tests = [
Expand Down
108 changes: 108 additions & 0 deletions tests/test_node_delegate_sig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from aeternity.contract_native import ContractNative
from aeternity import hashing, utils
from tests.conftest import random_domain
import pytest


contractAens = """contract DelegateTest =
// Transactions
stateful payable entrypoint signedPreclaim(addr : address,
chash : hash,
sign : signature) : unit =
AENS.preclaim(addr, chash, signature = sign)
stateful entrypoint signedClaim(addr : address,
name : string,
salt : int,
name_fee : int,
sign : signature) : unit =
AENS.claim(addr, name, salt, name_fee, signature = sign)
stateful entrypoint signedTransfer(owner : address,
new_owner : address,
name : string,
sign : signature) : unit =
AENS.transfer(owner, new_owner, name, signature = sign)
stateful entrypoint signedRevoke(owner : address,
name : string,
sign : signature) : unit =
AENS.revoke(owner, name, signature = sign)
"""


@pytest.mark.skip("blocked by https://github.com/aeternity/aepp-sdk-python/issues/306")
def test_node_contract_signature_delegation(compiler_fixture, chain_fixture):
compiler = compiler_fixture.COMPILER
account = chain_fixture.ALICE
bob = chain_fixture.BOB
contract_native = ContractNative(client=chain_fixture.NODE_CLI, source=contractAens, compiler=compiler, account=account)
contract_native.deploy()

assert(contract_native.address is not None)

# the contract_id
contract_id = contract_native.address
# node client
ae_cli = contract_native.client

# example name
name = random_domain(length=15)
c_id, salt = hashing.commitment_id(name, 9876)
name_ttl = 500000
client_ttl = 36000
name_fee = utils.amount_to_aettos("20AE")
print(f"name is {name}, commitment_id: {c_id}")

# aens calls
signature = ae_cli.delegate_name_preclaim_signature(account, contract_id)
call, r = contract_native.signedPreclaim(account.get_address(), c_id, signature)
assert(call.return_type == 'ok')
ae_cli.wait_for_confirmation(call.tx_hash)

signature = ae_cli.delegate_name_claim_signature(account, contract_id, name)
call, _ = contract_native.signedClaim(account.get_address(), name, salt, name_fee, signature)
assert(call.return_type == 'ok')

signature = ae_cli.delegate_name_transfer_signature(account, contract_id, name)
call, _ = contract_native.signedTransfer(account.get_address(), bob.get_address(), name, signature)
assert(call.return_type == 'ok')

signature = ae_cli.delegate_name_revoke_signature(bob, contract_id, name)
call, _ = contract_native.signedRevoke(bob.get_address(), name, signature)
assert(call.return_type == 'ok')



contractOracles = """contract DelegateTest =
type fee = int
type ttl = Chain.ttl
type query_t = string
type answer_t = int
type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t)
stateful payable entrypoint signedRegisterOracle(acct : address,
sign : signature,
qfee : fee,
ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl, signature = sign)
stateful payable entrypoint signedExtendOracle(o : oracle_id,
sign : signature, // Signed oracle address
ttl : ttl) : unit =
Oracle.extend(o, signature = sign, ttl)
datatype complexQuestion = Why(int) | How(string)
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
stateful entrypoint signedComplexOracle(question, sig) =
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
Oracle.get_answer(o, q)
"""

0 comments on commit e11d441

Please sign in to comment.