Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Condition and fulfillment in escrow #698

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Construction of Wallet throws an "Invalid Seed" error, if the secret is not decode-able.
- Rectify the incorrect usage of a transaction flag name: Update `TF_NO_DIRECT_RIPPLE` to `TF_NO_RIPPLE_DIRECT`

### Added
- Add a utility function `generate_escrow_cryptoconditions` to generate cryptographic condition and fulfillment for conditional escrows

## [2.5.0] - 2023-11-30

### Added
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ websockets = "^11.0"
Deprecated = "^1.2.13"
types-Deprecated = "^1.2.9"
pycryptodome = "^3.16.0"
cryptoconditions = "0.8.1"

[tool.poetry.dev-dependencies]
flake8 = "^3.8.4"
Expand Down
40 changes: 40 additions & 0 deletions snippets/send_escrow.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Example of how we can set up an escrow"""
from datetime import datetime
from os import urandom
from time import sleep

from xrpl.account import get_balance
from xrpl.clients import JsonRpcClient
from xrpl.models import AccountObjects, EscrowCreate, EscrowFinish
from xrpl.models.transactions.escrow_create import generate_escrow_cryptoconditions
from xrpl.transaction.reliable_submission import submit_and_wait
from xrpl.utils import datetime_to_ripple_time
from xrpl.wallet import generate_faucet_wallet
Expand Down Expand Up @@ -64,3 +66,41 @@
print("Balances of wallets after Escrow was sent:")
print(get_balance(wallet1.address, client))
print(get_balance(wallet2.address, client))

# Setup conditional escrows
cryptoCondition = generate_escrow_cryptoconditions(urandom(32))

create_tx = EscrowCreate(
account=wallet1.address,
destination=wallet2.address,
amount="1000000",
condition=cryptoCondition["condition"],
cancel_after=datetime_to_ripple_time(datetime.now()) + 100,
)

create_escrow_response = submit_and_wait(create_tx, client, wallet1)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have empirically observed that the generated cryptographic condition always begins with A0258020... and ends with ...810120. I don't have statistical numbers to prove my point, but the outputs from this library do not appear random, despite varying secret inputs.

print(create_escrow_response)

# Create an AccountObjects request and have the client call it to see if escrow exists
account_objects_request = AccountObjects(account=wallet1.address)
account_objects = (client.request(account_objects_request)).result["account_objects"]

print("Conditional Escrow object exists in wallet1's account:")
print(account_objects)

# Create an EscrowFinish transaction, then sign, autofill, and send it
finish_tx = EscrowFinish(
account=wallet1.address,
owner=wallet1.address,
offer_sequence=create_escrow_response.result["Sequence"],
fulfillment=cryptoCondition["fulfillment"],
condition=cryptoCondition["condition"],
)

submit_and_wait(finish_tx, client, wallet1)

# The fees for EscrowFinish transaction of a conditional escrows are higher.
# Additionally, the fees scale with the reference load on the server
print("Balances of wallets after Escrow was sent:")
print(get_balance(wallet1.address, client))
print(get_balance(wallet2.address, client))
48 changes: 47 additions & 1 deletion tests/unit/models/transactions/test_escrow_create.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from os import urandom
from unittest import TestCase

from xrpl.models.exceptions import XRPLModelException
from xrpl.models.transactions import EscrowCreate
from xrpl.models.transactions import EscrowCreate, EscrowFinish
from xrpl.models.transactions.escrow_create import generate_escrow_cryptoconditions

_OFFER_SEQUENCE = 1
_OWNER = "rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN"


class TestEscrowCreate(TestCase):
Expand All @@ -24,3 +29,44 @@ def test_final_after_less_than_cancel_after(self):
finish_after=finish_after,
sequence=sequence,
)

def test_escrow_condition_and_fulfillment(self):
account = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
amount = "100"
destination = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
fee = "0.00001"
sequence = 19048

# use os.urandom as the source of cryptographic randomness
condition, fulfillment = generate_escrow_cryptoconditions(urandom(32))

EscrowCreate(
account=account,
amount=amount,
destination=destination,
fee=fee,
sequence=sequence,
condition=condition,
)

# EscrowFinish without the fullfillment must throw an error
with self.assertRaises(XRPLModelException):
EscrowFinish(
account=account,
condition=condition,
fee=fee,
sequence=sequence,
offer_sequence=_OFFER_SEQUENCE,
owner=_OWNER,
)

# execute Escrow finish with the fulfillment
EscrowFinish(
account=account,
condition=condition,
fee=fee,
sequence=sequence,
fulfillment=fulfillment,
offer_sequence=_OFFER_SEQUENCE,
owner=_OWNER,
)
33 changes: 32 additions & 1 deletion xrpl/models/transactions/escrow_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
from __future__ import annotations # Requires Python 3.7+

from dataclasses import dataclass, field
from typing import Dict, Optional
from typing import Dict, Optional, TypedDict

# CK: TODO Find a py.typed or library stub for cryptoconditions
from cryptoconditions import PreimageSha256 # type: ignore
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let me know if you have any ideas to get mypy to work with a cryptographic library


from xrpl.models.amounts import Amount
from xrpl.models.required import REQUIRED
Expand Down Expand Up @@ -81,3 +84,31 @@ def _get_errors(self: EscrowCreate) -> Dict[str, str]:
] = "The finish_after time must be before the cancel_after time."

return errors


class CryptoConditions(TypedDict):
"""
A typed-dictionary containing the condition and the fulfillment for
conditional Escrows
"""

condition: str
fulfillment: str


def generate_escrow_cryptoconditions(secret: bytes) -> CryptoConditions:
"""Generate a condition and fulfillment for escrows

Args:
secret: Cryptographic source of randomness used to generate the condition and
fulfillment

Returns:
A pair of condition and fulfillment is returned

"""
fufill = PreimageSha256(preimage=secret)
return {
"condition": str.upper(fufill.condition_binary.hex()),
"fulfillment": str.upper(fufill.serialize_binary().hex()),
}
Loading