mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-16 11:28:14 +00:00
commit
ce7ed00eb9
@ -32,4 +32,4 @@ known_standard_library = micropython,ubinascii,ustruct,uctypes,utime,utimeq,trez
|
||||
[tool:pytest]
|
||||
addopts = --pyargs trezorlib.tests.device_tests
|
||||
xfail_strict = true
|
||||
run_xfail =
|
||||
run_xfail = ripple
|
||||
|
39
src/apps/ripple/README.md
Normal file
39
src/apps/ripple/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Ripple
|
||||
|
||||
MAINTAINER = Tomas Susanka <tomas.susanka@satoshilabs.com>
|
||||
|
||||
AUTHOR = Tomas Susanka <tomas.susanka@satoshilabs.com>
|
||||
|
||||
REVIEWER = Jan Pochyla <jan.pochyla@satoshilabs.com>
|
||||
|
||||
-----
|
||||
|
||||
## Documentation
|
||||
|
||||
Ripple's documentation can be found [here](https://developers.ripple.com/) and on the deprecated [wiki](https://wiki.ripple.com).
|
||||
|
||||
## Transactions
|
||||
|
||||
Ripple has different transaction types, see the [documentation](https://developers.ripple.com/transaction-formats.html) for the structure and the list of all transaction types. The concept is somewhat similar to Stellar. However, Stellar's transaction is composed of operations, whereas in Ripple each transaction is simply of some transaction type.
|
||||
|
||||
We do not support transaction types other than the [Payment](https://developers.ripple.com/payment.html) transaction, which represents the simple "A pays to B" scenario. Other transaction types might be added later on.
|
||||
|
||||
Non-XRP currencies are not supported. Float and negative amounts are not supported.
|
||||
|
||||
#### Transactions Explorer
|
||||
|
||||
[Bithomp](https://bithomp.com/) seems to work fine.
|
||||
|
||||
#### Submitting a transaction
|
||||
|
||||
You can use [ripple-lib](https://github.com/ripple/ripple-lib) and its [submit](https://github.com/ripple/ripple-lib/blob/develop/docs/index.md#submit) method to publish a transaction into the Ripple network. Python-trezor returns a serialized signed transaction, which is exactly what you provide as an argument into the submit function.
|
||||
|
||||
## Serialization format
|
||||
|
||||
Ripple uses its own [serialization format](https://wiki.ripple.com/Binary_Format). In a simple case, the first nibble of a first byte denotes the type and the second nibble the field. The actual data follow.
|
||||
|
||||
Our implementation in `serialize.py` is a simplification of the protocol tailored for the support of the Payment type exclusively.
|
||||
|
||||
## Tests
|
||||
|
||||
Unit tests are located in the `tests` directory, device tests are in the python-trezor repository.
|
19
src/apps/ripple/__init__.py
Normal file
19
src/apps/ripple/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
from trezor.messages.MessageType import RippleGetAddress, RippleSignTx
|
||||
from trezor.wire import protobuf_workflow, register
|
||||
|
||||
|
||||
def dispatch_RippleGetAddress(*args, **kwargs):
|
||||
from .get_address import get_address
|
||||
|
||||
return get_address(*args, **kwargs)
|
||||
|
||||
|
||||
def dispatch_RippleSignTx(*args, **kwargs):
|
||||
from .sign_tx import sign_tx
|
||||
|
||||
return sign_tx(*args, **kwargs)
|
||||
|
||||
|
||||
def boot():
|
||||
register(RippleGetAddress, protobuf_workflow, dispatch_RippleGetAddress)
|
||||
register(RippleSignTx, protobuf_workflow, dispatch_RippleSignTx)
|
33
src/apps/ripple/base58_ripple.py
Normal file
33
src/apps/ripple/base58_ripple.py
Normal file
@ -0,0 +1,33 @@
|
||||
from trezor.crypto import base58
|
||||
|
||||
# Ripple uses different 58 character alphabet than traditional base58
|
||||
_ripple_alphabet = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"
|
||||
|
||||
|
||||
def encode(data: bytes) -> str:
|
||||
"""
|
||||
Convert bytes to base58 encoded string.
|
||||
"""
|
||||
return base58.encode(data, alphabet=_ripple_alphabet)
|
||||
|
||||
|
||||
def decode(string: str) -> bytes:
|
||||
"""
|
||||
Convert base58 encoded string to bytes.
|
||||
"""
|
||||
return base58.decode(string, alphabet=_ripple_alphabet)
|
||||
|
||||
|
||||
def encode_check(data: bytes, digestfunc=base58.sha256d_32) -> str:
|
||||
"""
|
||||
Convert bytes to base58 encoded string, append checksum.
|
||||
"""
|
||||
return encode(data + digestfunc(data))
|
||||
|
||||
|
||||
def decode_check(string: str, digestfunc=base58.sha256d_32) -> bytes:
|
||||
"""
|
||||
Convert base58 encoded string to bytes and verify checksum.
|
||||
"""
|
||||
data = decode(string)
|
||||
return base58.verify_checksum(data, digestfunc)
|
22
src/apps/ripple/get_address.py
Normal file
22
src/apps/ripple/get_address.py
Normal file
@ -0,0 +1,22 @@
|
||||
from trezor.messages.RippleAddress import RippleAddress
|
||||
from trezor.messages.RippleGetAddress import RippleGetAddress
|
||||
|
||||
from . import helpers
|
||||
|
||||
from apps.common import seed
|
||||
from apps.common.display_address import show_address, show_qr
|
||||
|
||||
|
||||
async def get_address(ctx, msg: RippleGetAddress):
|
||||
node = await seed.derive_node(ctx, msg.address_n)
|
||||
pubkey = node.public_key()
|
||||
address = helpers.address_from_public_key(pubkey)
|
||||
|
||||
if msg.show_display:
|
||||
while True:
|
||||
if await show_address(ctx, address):
|
||||
break
|
||||
if await show_qr(ctx, address.upper()):
|
||||
break
|
||||
|
||||
return RippleAddress(address=address)
|
47
src/apps/ripple/helpers.py
Normal file
47
src/apps/ripple/helpers.py
Normal file
@ -0,0 +1,47 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor.crypto.hashlib import ripemd160, sha256
|
||||
|
||||
from . import base58_ripple
|
||||
|
||||
# HASH_TX_ID = const(0x54584E00) # 'TXN'
|
||||
HASH_TX_SIGN = const(0x53545800) # 'STX'
|
||||
# HASH_TX_SIGN_TESTNET = const(0x73747800) # 'stx'
|
||||
|
||||
# https://developers.ripple.com/basic-data-types.html#specifying-currency-amounts
|
||||
DIVISIBILITY = const(6) # 1000000 drops equal 1 XRP
|
||||
|
||||
# https://developers.ripple.com/transaction-cost.html
|
||||
MIN_FEE = const(10)
|
||||
# max is not defined officially but we check to make sure
|
||||
MAX_FEE = const(1000000) # equals 1 XRP
|
||||
|
||||
FLAG_FULLY_CANONICAL = 0x80000000
|
||||
|
||||
|
||||
def address_from_public_key(pubkey: bytes) -> str:
|
||||
"""Extracts public key from an address
|
||||
|
||||
Ripple address is in format:
|
||||
<1-byte ripple flag> <20-bytes account id> <4-bytes dSHA-256 checksum>
|
||||
|
||||
- 1-byte flag is 0x00 which is 'r' (Ripple uses its own base58 alphabet)
|
||||
- 20-bytes account id is a ripemd160(sha256(pubkey))
|
||||
- checksum is first 4 bytes of double sha256(data)
|
||||
|
||||
see https://developers.ripple.com/accounts.html#address-encoding
|
||||
"""
|
||||
"""Returns the Ripple address created using base58"""
|
||||
h = sha256(pubkey).digest()
|
||||
h = ripemd160(h).digest()
|
||||
|
||||
address = bytearray()
|
||||
address.append(0x00) # 'r'
|
||||
address.extend(h)
|
||||
return base58_ripple.encode_check(bytes(address))
|
||||
|
||||
|
||||
def decode_address(address: str):
|
||||
"""Returns so called Account ID"""
|
||||
adr = base58_ripple.decode_check(address)
|
||||
return adr[1:]
|
25
src/apps/ripple/layout.py
Normal file
25
src/apps/ripple/layout.py
Normal file
@ -0,0 +1,25 @@
|
||||
from trezor import ui
|
||||
from trezor.messages import ButtonRequestType
|
||||
from trezor.ui.text import Text
|
||||
from trezor.utils import format_amount
|
||||
|
||||
from . import helpers
|
||||
|
||||
from apps.common.confirm import require_confirm, require_hold_to_confirm
|
||||
from apps.common.display_address import split_address
|
||||
|
||||
|
||||
async def require_confirm_fee(ctx, fee):
|
||||
text = Text("Confirm fee", ui.ICON_SEND, icon_color=ui.GREEN)
|
||||
text.normal("Transaction fee:")
|
||||
text.bold(format_amount(fee, helpers.DIVISIBILITY) + " XRP")
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
|
||||
|
||||
async def require_confirm_tx(ctx, to, value):
|
||||
|
||||
text = Text("Confirm sending", ui.ICON_SEND, icon_color=ui.GREEN)
|
||||
text.bold(format_amount(value, helpers.DIVISIBILITY) + " XRP")
|
||||
text.normal("to")
|
||||
text.mono(*split_address(to))
|
||||
return await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)
|
121
src/apps/ripple/serialize.py
Normal file
121
src/apps/ripple/serialize.py
Normal file
@ -0,0 +1,121 @@
|
||||
# Serializes into the Ripple Format
|
||||
#
|
||||
# Inspired by https://github.com/miracle2k/ripple-python and https://github.com/ripple/ripple-lib
|
||||
# Docs at https://wiki.ripple.com/Binary_Format and https://developers.ripple.com/transaction-common-fields.html
|
||||
#
|
||||
# The first four bits specify the field type (int16, int32, account..)
|
||||
# the other four the record type (amount, fee, destination..) and then
|
||||
# the actual data follow. This currently only supports the Payment
|
||||
# transaction type and the fields that are required for it.
|
||||
#
|
||||
from trezor.messages.RippleSignTx import RippleSignTx
|
||||
|
||||
from . import helpers
|
||||
|
||||
FIELD_TYPE_INT16 = 1
|
||||
FIELD_TYPE_INT32 = 2
|
||||
FIELD_TYPE_AMOUNT = 6
|
||||
FIELD_TYPE_VL = 7
|
||||
FIELD_TYPE_ACCOUNT = 8
|
||||
|
||||
FIELDS_MAP = {
|
||||
"account": {"type": FIELD_TYPE_ACCOUNT, "key": 1},
|
||||
"amount": {"type": FIELD_TYPE_AMOUNT, "key": 1},
|
||||
"destination": {"type": FIELD_TYPE_ACCOUNT, "key": 3},
|
||||
"fee": {"type": FIELD_TYPE_AMOUNT, "key": 8},
|
||||
"sequence": {"type": FIELD_TYPE_INT32, "key": 4},
|
||||
"type": {"type": FIELD_TYPE_INT16, "key": 2},
|
||||
"signingPubKey": {"type": FIELD_TYPE_VL, "key": 3},
|
||||
"flags": {"type": FIELD_TYPE_INT32, "key": 2},
|
||||
"txnSignature": {"type": FIELD_TYPE_VL, "key": 4},
|
||||
"lastLedgerSequence": {"type": FIELD_TYPE_INT32, "key": 27},
|
||||
}
|
||||
|
||||
TRANSACTION_TYPES = {"Payment": 0}
|
||||
|
||||
|
||||
def serialize(msg: RippleSignTx, source_address: str, pubkey=None, signature=None):
|
||||
w = bytearray()
|
||||
# must be sorted numerically first by type and then by name
|
||||
write(w, FIELDS_MAP["type"], TRANSACTION_TYPES["Payment"])
|
||||
write(w, FIELDS_MAP["flags"], msg.flags)
|
||||
write(w, FIELDS_MAP["sequence"], msg.sequence)
|
||||
write(w, FIELDS_MAP["lastLedgerSequence"], msg.last_ledger_sequence)
|
||||
write(w, FIELDS_MAP["amount"], msg.payment.amount)
|
||||
write(w, FIELDS_MAP["fee"], msg.fee)
|
||||
write(w, FIELDS_MAP["signingPubKey"], pubkey)
|
||||
write(w, FIELDS_MAP["txnSignature"], signature)
|
||||
write(w, FIELDS_MAP["account"], source_address)
|
||||
write(w, FIELDS_MAP["destination"], msg.payment.destination)
|
||||
return w
|
||||
|
||||
|
||||
def write(w: bytearray, field: dict, value):
|
||||
if value is None:
|
||||
return
|
||||
write_type(w, field)
|
||||
if field["type"] == FIELD_TYPE_INT16:
|
||||
w.extend(value.to_bytes(2, "big"))
|
||||
elif field["type"] == FIELD_TYPE_INT32:
|
||||
w.extend(value.to_bytes(4, "big"))
|
||||
elif field["type"] == FIELD_TYPE_AMOUNT:
|
||||
w.extend(serialize_amount(value))
|
||||
elif field["type"] == FIELD_TYPE_ACCOUNT:
|
||||
write_bytes(w, helpers.decode_address(value))
|
||||
elif field["type"] == FIELD_TYPE_VL:
|
||||
write_bytes(w, value)
|
||||
else:
|
||||
raise ValueError("Unknown field type")
|
||||
|
||||
|
||||
def write_type(w: bytearray, field: dict):
|
||||
if field["key"] <= 0xf:
|
||||
w.append((field["type"] << 4) | field["key"])
|
||||
else:
|
||||
# this concerns two-bytes fields such as lastLedgerSequence
|
||||
w.append(field["type"] << 4)
|
||||
w.append(field["key"])
|
||||
|
||||
|
||||
def serialize_amount(value: int) -> bytearray:
|
||||
if value < 0 or isinstance(value, float):
|
||||
raise ValueError("Only positive integers are supported")
|
||||
if value > 100000000000: # max allowed value
|
||||
raise ValueError("Value is larger than 100000000000")
|
||||
|
||||
b = bytearray(value.to_bytes(8, "big"))
|
||||
# Clear first bit to indicate XRP
|
||||
b[0] &= 0x7f
|
||||
# Set second bit to indicate positive number
|
||||
b[0] |= 0x40
|
||||
return b
|
||||
|
||||
|
||||
def write_bytes(w: bytearray, value: bytes):
|
||||
"""Serialize a variable length bytes."""
|
||||
serialize_varint(w, len(value))
|
||||
w.extend(value)
|
||||
|
||||
|
||||
def serialize_varint(w, val):
|
||||
"""https://ripple.com/wiki/Binary_Format#Variable_Length_Data_Encoding"""
|
||||
|
||||
def rshift(val, n):
|
||||
# http://stackoverflow.com/a/5833119/15677
|
||||
return (val % 0x100000000) >> n
|
||||
|
||||
assert val >= 0
|
||||
|
||||
b = bytearray()
|
||||
if val < 192:
|
||||
b.append(val)
|
||||
elif val <= 12480:
|
||||
val -= 193
|
||||
b.extend([193 + rshift(val, 8), val & 0xff])
|
||||
elif val <= 918744:
|
||||
val -= 12481
|
||||
b.extend([241 + rshift(val, 16), rshift(val, 8) & 0xff, val & 0xff])
|
||||
else:
|
||||
raise ValueError("Variable integer overflow.")
|
||||
|
||||
w.extend(b)
|
77
src/apps/ripple/sign_tx.py
Normal file
77
src/apps/ripple/sign_tx.py
Normal file
@ -0,0 +1,77 @@
|
||||
from trezor.crypto import der
|
||||
from trezor.crypto.curve import secp256k1
|
||||
from trezor.crypto.hashlib import sha512
|
||||
from trezor.messages.RippleSignedTx import RippleSignedTx
|
||||
from trezor.messages.RippleSignTx import RippleSignTx
|
||||
from trezor.wire import ProcessError
|
||||
|
||||
from . import helpers, layout
|
||||
from .serialize import serialize
|
||||
|
||||
from apps.common import seed
|
||||
|
||||
|
||||
async def sign_tx(ctx, msg: RippleSignTx):
|
||||
validate(msg)
|
||||
node = await seed.derive_node(ctx, msg.address_n)
|
||||
source_address = helpers.address_from_public_key(node.public_key())
|
||||
|
||||
set_canonical_flag(msg)
|
||||
tx = serialize(msg, source_address, pubkey=node.public_key())
|
||||
to_sign = get_network_prefix() + tx
|
||||
|
||||
check_fee(msg.fee)
|
||||
await layout.require_confirm_fee(ctx, msg.fee)
|
||||
await layout.require_confirm_tx(ctx, msg.payment.destination, msg.payment.amount)
|
||||
|
||||
signature = ecdsa_sign(node.private_key(), first_half_of_sha512(to_sign))
|
||||
tx = serialize(msg, source_address, pubkey=node.public_key(), signature=signature)
|
||||
return RippleSignedTx(signature, tx)
|
||||
|
||||
|
||||
def check_fee(fee: int):
|
||||
if fee < helpers.MIN_FEE or fee > helpers.MAX_FEE:
|
||||
raise ProcessError("Fee must be in the range of 10 to 10,000 drops")
|
||||
|
||||
|
||||
def get_network_prefix():
|
||||
"""Network prefix is prepended before the transaction and public key is included"""
|
||||
return helpers.HASH_TX_SIGN.to_bytes(4, "big")
|
||||
|
||||
|
||||
def first_half_of_sha512(b):
|
||||
"""First half of SHA512, which Ripple uses"""
|
||||
hash = sha512(b)
|
||||
return hash.digest()[:32]
|
||||
|
||||
|
||||
def ecdsa_sign(private_key: bytes, digest: bytes) -> bytes:
|
||||
"""Signs and encodes signature into DER format"""
|
||||
signature = secp256k1.sign(private_key, digest)
|
||||
sig_der = der.encode_seq((signature[1:33], signature[33:65]))
|
||||
return sig_der
|
||||
|
||||
|
||||
def set_canonical_flag(msg: RippleSignTx):
|
||||
"""
|
||||
Our ECDSA implementation already returns fully-canonical signatures,
|
||||
so we're enforcing it in the transaction using the designated flag
|
||||
- see https://wiki.ripple.com/Transaction_Malleability#Using_Fully-Canonical_Signatures
|
||||
- see https://github.com/trezor/trezor-crypto/blob/3e8974ff8871263a70b7fbb9a27a1da5b0d810f7/ecdsa.c#L791
|
||||
"""
|
||||
if msg.flags is None:
|
||||
msg.flags = 0
|
||||
msg.flags |= helpers.FLAG_FULLY_CANONICAL
|
||||
|
||||
|
||||
def validate(msg: RippleSignTx):
|
||||
if None in (
|
||||
msg.fee,
|
||||
msg.sequence,
|
||||
msg.payment,
|
||||
msg.payment.amount,
|
||||
msg.payment.destination,
|
||||
):
|
||||
raise ProcessError(
|
||||
"Some of the required fields are missing (fee, sequence, payment.amount, payment.destination)"
|
||||
)
|
@ -16,9 +16,9 @@ import apps.ethereum
|
||||
import apps.lisk
|
||||
import apps.nem
|
||||
import apps.stellar
|
||||
import apps.ripple
|
||||
import apps.cardano
|
||||
|
||||
|
||||
if __debug__:
|
||||
import apps.debug
|
||||
else:
|
||||
@ -32,6 +32,7 @@ apps.ethereum.boot()
|
||||
apps.lisk.boot()
|
||||
apps.nem.boot()
|
||||
apps.stellar.boot()
|
||||
apps.ripple.boot()
|
||||
apps.cardano.boot()
|
||||
if __debug__:
|
||||
apps.debug.boot()
|
||||
|
@ -17,7 +17,7 @@
|
||||
_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
|
||||
|
||||
def encode(data: bytes) -> str:
|
||||
def encode(data: bytes, alphabet=_alphabet) -> str:
|
||||
"""
|
||||
Convert bytes to base58 encoded string.
|
||||
"""
|
||||
@ -33,22 +33,22 @@ def encode(data: bytes) -> str:
|
||||
result = ""
|
||||
while acc > 0:
|
||||
acc, mod = divmod(acc, 58)
|
||||
result += _alphabet[mod]
|
||||
result += alphabet[mod]
|
||||
|
||||
return "".join((c for c in reversed(result + _alphabet[0] * (origlen - newlen))))
|
||||
return "".join((c for c in reversed(result + alphabet[0] * (origlen - newlen))))
|
||||
|
||||
|
||||
def decode(string: str) -> bytes:
|
||||
def decode(string: str, alphabet=_alphabet) -> bytes:
|
||||
"""
|
||||
Convert base58 encoded string to bytes.
|
||||
"""
|
||||
origlen = len(string)
|
||||
string = string.lstrip(_alphabet[0])
|
||||
string = string.lstrip(alphabet[0])
|
||||
newlen = len(string)
|
||||
|
||||
p, acc = 1, 0
|
||||
for c in reversed(string):
|
||||
acc += p * _alphabet.index(c)
|
||||
acc += p * alphabet.index(c)
|
||||
p *= 58
|
||||
|
||||
result = []
|
||||
@ -83,8 +83,12 @@ def decode_check(string: str, digestfunc=sha256d_32) -> bytes:
|
||||
Convert base58 encoded string to bytes and verify checksum.
|
||||
"""
|
||||
result = decode(string)
|
||||
return verify_checksum(result, digestfunc)
|
||||
|
||||
|
||||
def verify_checksum(data: bytes, digestfunc) -> bytes:
|
||||
digestlen = len(digestfunc(b""))
|
||||
result, check = result[:-digestlen], result[-digestlen:]
|
||||
result, check = data[:-digestlen], data[-digestlen:]
|
||||
|
||||
if check != digestfunc(result):
|
||||
raise ValueError("Invalid checksum")
|
||||
|
19
tests/test_apps.ripple.pubkey_to_address.py
Normal file
19
tests/test_apps.ripple.pubkey_to_address.py
Normal file
@ -0,0 +1,19 @@
|
||||
from common import *
|
||||
from apps.ripple.helpers import address_from_public_key
|
||||
|
||||
|
||||
class TestStellarPubkeyToAddress(unittest.TestCase):
|
||||
|
||||
def test_pubkey_to_address(self):
|
||||
addr = address_from_public_key(unhexlify('ed9434799226374926eda3b54b1b461b4abf7237962eae18528fea67595397fa32'))
|
||||
self.assertEqual(addr, 'rDTXLQ7ZKZVKz33zJbHjgVShjsBnqMBhmN')
|
||||
|
||||
addr = address_from_public_key(unhexlify('03e2b079e9b09ae8916da8f5ee40cbda9578dbe7c820553fe4d5f872eec7b1fdd4'))
|
||||
self.assertEqual(addr, 'rhq549rEtUrJowuxQC2WsHNGLjAjBQdAe8')
|
||||
|
||||
addr = address_from_public_key(unhexlify('0282ee731039929e97db6aec242002e9aa62cd62b989136df231f4bb9b8b7c7eb2'))
|
||||
self.assertEqual(addr, 'rKzE5DTyF9G6z7k7j27T2xEas2eMo85kmw')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
77
tests/test_apps.ripple.serializer.py
Normal file
77
tests/test_apps.ripple.serializer.py
Normal file
@ -0,0 +1,77 @@
|
||||
from common import *
|
||||
from apps.ripple.serialize import serialize
|
||||
from apps.ripple.serialize import serialize_amount
|
||||
from apps.ripple.sign_tx import get_network_prefix
|
||||
from trezor.messages.RippleSignTx import RippleSignTx
|
||||
from trezor.messages.RipplePayment import RipplePayment
|
||||
|
||||
|
||||
class TestRippleSerializer(unittest.TestCase):
|
||||
|
||||
def test_amount(self):
|
||||
# https://github.com/ripple/ripple-binary-codec/blob/4581f1b41e712f545ba08be15e188a557c731ecf/test/fixtures/data-driven-tests.json#L2494
|
||||
assert serialize_amount(0) == unhexlify('4000000000000000')
|
||||
assert serialize_amount(1) == unhexlify('4000000000000001')
|
||||
assert serialize_amount(93493429243) == unhexlify('40000015c4a483fb')
|
||||
with self.assertRaises(ValueError):
|
||||
serialize_amount(1000000000000000000) # too large
|
||||
with self.assertRaises(ValueError):
|
||||
serialize_amount(-1) # negative not supported
|
||||
with self.assertRaises(ValueError):
|
||||
serialize_amount(1.1) # float numbers not supported
|
||||
|
||||
def test_transactions(self):
|
||||
# from https://github.com/miracle2k/ripple-python
|
||||
source_address = 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi'
|
||||
payment = RipplePayment(200000000, 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV')
|
||||
common = RippleSignTx(None, 10, None, 1, None, payment)
|
||||
assert serialize(common, source_address) == unhexlify('120000240000000161400000000bebc20068400000000000000a811450f97a072f1c4357f1ad84566a609479d927c9428314550fc62003e785dc231a1058a05e56e3f09cf4e6')
|
||||
|
||||
source_address = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV'
|
||||
payment = RipplePayment(1, 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi')
|
||||
common = RippleSignTx(None, 99, None, 99, None, payment)
|
||||
assert serialize(common, source_address) == unhexlify('12000024000000636140000000000000016840000000000000638114550fc62003e785dc231a1058a05e56e3f09cf4e6831450f97a072f1c4357f1ad84566a609479d927c942')
|
||||
|
||||
# https://github.com/ripple/ripple-binary-codec/blob/4581f1b41e712f545ba08be15e188a557c731ecf/test/fixtures/data-driven-tests.json#L1579
|
||||
source_address = 'r9TeThyi5xiuUUrFjtPKZiHcDxs7K9H6Rb'
|
||||
payment = RipplePayment(25000000, 'r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C')
|
||||
common = RippleSignTx(None, 10, 0, 2, None, payment)
|
||||
assert serialize(common, source_address) == unhexlify('120000220000000024000000026140000000017d784068400000000000000a81145ccb151f6e9d603f394ae778acf10d3bece874f68314e851bbbe79e328e43d68f43445368133df5fba5a')
|
||||
|
||||
# https://github.com/ripple/ripple-binary-codec/blob/4581f1b41e712f545ba08be15e188a557c731ecf/test/fixtures/data-driven-tests.json#L1651
|
||||
source_address = 'rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e'
|
||||
payment = RipplePayment(200000, 'rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ')
|
||||
common = RippleSignTx(None, 15, 0, 144, None, payment)
|
||||
# 201b005ee9ba removed from the test vector because last ledger sequence is not supported
|
||||
assert serialize(common, source_address) == unhexlify('12000022000000002400000090614000000000030d4068400000000000000f8114aa1bd19d9e87be8069fdbf6843653c43837c03c6831467fe6ec28e0464dd24fb2d62a492aac697cfad02')
|
||||
|
||||
# https://github.com/ripple/ripple-binary-codec/blob/4581f1b41e712f545ba08be15e188a557c731ecf/test/fixtures/data-driven-tests.json#L1732
|
||||
source_address = 'r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C'
|
||||
payment = RipplePayment(25000000, 'rBqSFEFg2B6GBMobtxnU1eLA1zbNC9NDGM')
|
||||
common = RippleSignTx(None, 12, 0, 1, None, payment)
|
||||
# 2ef72d50ca removed from the test vector because destination tag is not supported
|
||||
assert serialize(common, source_address) == unhexlify('120000220000000024000000016140000000017d784068400000000000000c8114e851bbbe79e328e43d68f43445368133df5fba5a831476dac5e814cd4aa74142c3ab45e69a900e637aa2')
|
||||
|
||||
def test_transactions_for_signing(self):
|
||||
# https://github.com/ripple/ripple-binary-codec/blob/4581f1b41e712f545ba08be15e188a557c731ecf/test/signing-data-encoding-test.js
|
||||
source_address = 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ'
|
||||
payment = RipplePayment(1000, 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')
|
||||
common = RippleSignTx(None, 10, 2147483648, 1, None, payment)
|
||||
|
||||
tx = serialize(common, source_address, pubkey=unhexlify('ed5f5ac8b98974a3ca843326d9b88cebd0560177b973ee0b149f782cfaa06dc66a'))
|
||||
tx = get_network_prefix() + tx
|
||||
|
||||
assert tx[0:4] == unhexlify('53545800') # signing prefix
|
||||
assert tx[4:7] == unhexlify('120000') # transaction type
|
||||
assert tx[7:12] == unhexlify('2280000000') # flags
|
||||
assert tx[12:17] == unhexlify('2400000001') # sequence
|
||||
assert tx[17:26] == unhexlify('6140000000000003e8') # amount
|
||||
assert tx[26:35] == unhexlify('68400000000000000a') # fee
|
||||
assert tx[35:70] == unhexlify('7321ed5f5ac8b98974a3ca843326d9b88cebd0560177b973ee0b149f782cfaa06dc66a') # singing pub key
|
||||
assert tx[70:92] == unhexlify('81145b812c9d57731e27a2da8b1830195f88ef32a3b6') # account
|
||||
assert tx[92:114] == unhexlify('8314b5f762798a53d543a014caf8b297cff8f2f937e8') # destination
|
||||
assert len(tx[114:]) == 0 # that's it
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user