feat: tests sign

Signed-off-by: Coline <coline.seguret@ledger.fr>
This commit is contained in:
Coline
2022-04-11 10:47:15 +02:00
parent 104bfee2a9
commit 35363e256d
7 changed files with 205 additions and 32 deletions

View File

@@ -50,7 +50,7 @@ This command returns specific application configuration
|CLA|INS|P1|P2|Lc|Le|
|---|---|--|--|--|--|
|E0|06|00|00|00|04|
|E0|06|00|00|00|00|
:inbox_tray: input data
@@ -63,7 +63,23 @@ None
|0x01 : arbitrary data signature enabled by user<br/>0x02 : ERC 20 Token information needs to be provided externally|1|
|Application major version|1|
|Application minor version|1|
|Application patch version|1|
|Application patch version|1|
Exemple:
CLA: E0
INS: 06
P1 : 00
P2 : 00
Lc : 00
Le : 00
|CLA|INS|P1|P2|Lc|Le|
|---|---|--|--|--|--|
|E0|06|00|00|00|00|
-> E0 06 00 00 00 00
</details>
<br/>
@@ -80,6 +96,7 @@ The address can be optionally checked on the device before being returned.
Usefull link:
- [HD Wallet by ledger](https://www.ledger.com/academy/crypto/what-are-hierarchical-deterministic-hd-wallets)
- [BIP-044](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
- [psd-application](https://developers.ledger.com/docs/nano-app/psd-applications/)
|CLA|INS|P1 |P2 |Lc |Le |
|---|---|-------------------------------------------------|--------------------------------|----------|----------|
@@ -170,6 +187,8 @@ This command has been supported since firmware version 1.6.0
## SIGN
- [RLP encoding](https://medium.com/coinmonks/data-structure-in-ethereum-episode-1-recursive-length-prefix-rlp-encoding-decoding-d1016832f919)
### SIGN ETH TRANSACTION
<details>
@@ -353,7 +372,7 @@ The signature is computed on
len(pluginName) || pluginName || contractAddress || methodSelector
signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f0976b22d3388a9a5e446ac62d25cf725b62a2555b2dd464a4da0ab2f4d506820543af1d242470b1b1a969a27578f353
signed by the following secp256k1 public key `0482bbf2f34f367b2e5bc21847b6566f21f0976b22d3388a9a5e446ac62d25cf725b62a2555b2dd464a4da0ab2f4d506820543af1d242470b1b1a969a27578f353`
|CLA|INS|P1|P2|Lc|Le|
|---|---|--|--|--|--|

View File

@@ -28,12 +28,12 @@ pytest tests/speculos/
you will find the list of apdu [here](../../doc/apdu.md)
- Get
- GET APP CONFIGURATIOn
- [X] Simple test
- GET ETH PUBLIC ADDRESS
- [X] Test get key of coin (Ether, Dai)
- [ ] Test get key of coin (Ether, Dai) with display
- [ ] Test without chain code
- GET APP CONFIGURATION ( 1 test )
- Get the configuration
- GET ETH PUBLIC ADDRESS ( 3 tests )
- Ether coin without display
- Dai coin with display
- Dai coin with display and reject
- GET ETH2 PUBLIC KEY
- [ ] Test get key
- [ ] Test get key with display

View File

@@ -1,3 +1,6 @@
from ast import List
from contextlib import contextmanager
from ctypes.wintypes import INT
import struct
from typing import Tuple
@@ -24,7 +27,7 @@ class BoilerplateCommand:
except ApduException as error:
raise DeviceException(error_code=error.sw, ins=InsType.INS_GET_VERSION)
# response = MAJOR (1) || MINOR (1) || PATCH (1)
# response = FLAG (1) || MAJOR (1) || MINOR (1) || PATCH (1)
assert len(response) == 4
info, major, minor, patch = struct.unpack(
@@ -34,12 +37,17 @@ class BoilerplateCommand:
return info, major, minor, patch
def get_public_key(self, bip32_path: str, display: bool = False) -> Tuple[bytes, bytes, bytes]:
@contextmanager
def get_public_key(self, bip32_path: str, display: bool = False, result: List = list()) -> Tuple[bytes, bytes, bytes]:
try:
response = self.client._apdu_exchange(
self.builder.get_public_key(bip32_path=bip32_path,
display=display)
) # type: int, bytes
chunk: bytes = self.builder.get_public_key(bip32_path=bip32_path, display=display)
with self.client.apdu_exchange_nowait(cla=chunk[0], ins=chunk[1],
p1=chunk[2], p2=chunk[3],
data=chunk[5:]) as exchange:
yield exchange
response: bytes = exchange.receive()
except ApduException as error:
raise DeviceException(error_code=error.sw, ins=InsType.INS_GET_PUBLIC_KEY)
@@ -64,8 +72,36 @@ class BoilerplateCommand:
chain_code: bytes = response[offset:]
assert len(response) == 1 + pub_key_len + 1 + eth_addr_len + 32 # 32 -> chain_code_len
result.append(uncompressed_addr_len)
result.append(eth_addr)
result.append(chain_code)
return uncompressed_addr_len, eth_addr, chain_code
def simple_sign_tx(self, bip32_path: str, transaction) -> Tuple[int, int, int]:
chunk: bytes = self.builder.simple_sign_tx(bip32_path=bip32_path, transaction=transaction)
response: bytes = b""
with self.client.apdu_exchange_nowait(cla=chunk[0], ins=chunk[1],
p1=chunk[2], p2=chunk[3],
data=chunk[5:]) as exchange:
# Review Transaction
self.client.press_and_release('right')
# Address 1/3, 2/3, 3/3
self.client.press_and_release('right')
self.client.press_and_release('right')
self.client.press_and_release('right')
# Amount
self.client.press_and_release('right')
# Approve
self.client.press_and_release('both')
response = exchange.receive()
# response = V (1) || R (32) || S (32)
assert len(response) == 65
v, r, s = struct.unpack("BII", response)
return v, r, s
def sign_tx(self, bip32_path: str, transaction: Transaction) -> Tuple[int, bytes]:
sw: int

View File

@@ -3,6 +3,8 @@ import logging
import struct
from typing import List, Tuple, Union, Iterator, cast
import rlp
from boilerplate_client.transaction import Transaction
from boilerplate_client.utils import bip32_path_from_string
@@ -29,9 +31,17 @@ def chunkify(data: bytes, chunk_len: int) -> Iterator[Tuple[bool, bytes]]:
class InsType(enum.IntEnum):
INS_GET_PUBLIC_KEY = 0x02
INS_SIGN_TX = 0x04
INS_GET_CONFIGURATION = 0x06
INS_GET_PUBLIC_KEY = 0x02
INS_SIGN_TX = 0x04
INS_GET_CONFIGURATION = 0x06
INS_SIGN_PERSONAL_TX = 0x08
INS_PROVIDE_ERC20 = 0x0A
INS_SIGN_EIP712 = 0x0c
INS_ETH2_GET_PUBLIC_KEY = 0x0E
INS_SET_ETH2_WITHDRAWAL = 0x10
INS_SET_EXTERNAL_PLUGIN = 0x12
INS_PROVIDE_NFT_INFORMATION = 0x14
INS_SET_PLUGIN = 0x16
class BoilerplateCommandBuilder:
@@ -185,3 +195,35 @@ class BoilerplateCommandBuilder:
p1=0x00,
p2=0x00,
cdata=chunk)
def simple_sign_tx(self, bip32_path: str, transaction) -> bytes:
"""Command builder for INS_SIGN_TX.
Parameters
----------
bip32_path : str
String representation of BIP32 path.
transaction : Transaction
Representation of the transaction to be signed.
Yields
-------
bytes
APDU command chunk for INS_SIGN_TX.
"""
bip32_paths: List[bytes] = bip32_path_from_string(bip32_path)
cdata: bytes = b"".join([
len(bip32_paths).to_bytes(1, byteorder="big"),
*bip32_paths,
rlp.encode(transaction)
])
print(cdata)
return self.serialize(cla=self.CLA,
ins=InsType.INS_SIGN_TX,
p1=0x00,
p2=0x00,
cdata=cdata)

View File

@@ -0,0 +1,35 @@
@startuml Network
enum InsType <int> {
INS_GET_PUBLIC_KEY = 0x02
INS_SIGN_TX = 0x04
INS_GET_CONFIGURATION = 0x06
INS_SIGN_PERSONAL_TX = 0x08
INS_PROVIDE_ERC20 = 0x0A
INS_SIGN_EIP712 = 0x0c
INS_ETH2_GET_PUBLIC_KEY = 0x0E
INS_SET_ETH2_WITHDRAWAL = 0x10
INS_SET_EXTERNAL_PLUGIN = 0x12
INS_PROVIDE_NFT_INFORMATION = 0x14
INS_SET_PLUGIN = 0x16
}
class BoilerPlateCommandBuilder {
+bytes serialize(cla int, ins InsType, p1 int, p2 int, cdata bytes)
____
.. APDU Builder..
+get_configuration() -> bytes
+get_public_key(bip32_path str, display bool) -> bytes
}
class BoilerplateCommand {
+get_configuration() -> Tuple[int, int, int]
+get_public_key(bip32_path str, diplay bool) -> Tuple[bytes, bytes, bytes]
}
class Transaction {
+serialize() -> bytes
+from_bytes(cls, hexa: Union[bytes, BytesIO])
}
@enduml

View File

@@ -1,16 +1,18 @@
from cgitb import reset
from pickle import TRUE
from typing import Tuple
import boilerplate_client
def test_get_public_key(cmd):
# ETHER COIN
uncompressed_addr_len, eth_addr, chain_code = cmd.get_public_key(
bip32_path="44'/60'/1'/0/0",
display=False
) # type: bytes, bytes, bytes
# ETHER COIN without display
result: list = []
with cmd.get_public_key(bip32_path="44'/60'/1'/0/0", display=False, result=result) as exchange:
pass
print("HERE", uncompressed_addr_len)
uncompressed_addr_len, eth_addr, chain_code = result
assert len(uncompressed_addr_len) == 65
assert len(eth_addr) == 40
@@ -20,14 +22,20 @@ def test_get_public_key(cmd):
assert eth_addr == b'463e4e114AA57F54f2Fd2C3ec03572C6f75d84C2'
assert chain_code == b'\xaf\x89\xcd)\xea${8I\xec\xc80\xc2\xc8\x94\\e1\xd6P\x87\x07?\x9f\xd09\x00\xa0\xea\xa7\x96\xc8'
# DAI COIN
uncompressed_addr_len, eth_addr, chain_code = cmd.get_public_key(
bip32_path="44'/700'/1'/0/0",
display=False
) # type: bytes, bytes, bytes
print("HERE2", uncompressed_addr_len)
# DAI COIN with display
result: list = []
with cmd.get_public_key(bip32_path="44'/700'/1'/0/0", display=True, result=result) as exchange:
cmd.client.press_and_release('right')
# Verify address
cmd.client.press_and_release('right')
# Address 1/3, 2/3, 3/3
cmd.client.press_and_release('right')
cmd.client.press_and_release('right')
cmd.client.press_and_release('right')
# Approved
cmd.client.press_and_release('both')
uncompressed_addr_len, eth_addr, chain_code = result
assert len(uncompressed_addr_len) == 65
assert len(eth_addr) == 40
assert len(chain_code) == 32
@@ -35,3 +43,23 @@ def test_get_public_key(cmd):
assert uncompressed_addr_len == b'\x04V\x8a\x15\xdc\xed\xc8[\x16\x17\x8d\xaf\xcax\x91v~{\x9c\x06\xba\xaa\xde\xf4\xe7\x9f\x86\x1d~\xed)\xdc\n8\x9c\x84\xf01@E\x13]\xd7~6\x8e\x8e\xabb-\xad\xcdo\xc3Fw\xb7\xc8y\xdbQ/\xc3\xe5\x18'
assert eth_addr == b'Ba9A9aED0a1AbBE1da1155F64e73e57Af7995880'
assert chain_code == b'4\xaa\x95\xf4\x02\x12\x12-T\x155\x86\xed\xc5\x0b\x1d8\x81\xae\xce\xbd\x1a\xbbv\x9a\xc7\xd5\x1a\xd0KT\xe4'
def test_reject_get_public_key(cmd):
try:
# DAI COIN with display
result: list = []
with cmd.get_public_key(bip32_path="44'/700'/1'/0/0", display=True, result=result) as exchange:
cmd.client.press_and_release('right')
# Verify address
cmd.client.press_and_release('right')
# Address 1/3, 2/3, 3/3
cmd.client.press_and_release('right')
cmd.client.press_and_release('right')
cmd.client.press_and_release('right')
# Reject
cmd.client.press_and_release('right')
cmd.client.press_and_release('both')
except boilerplate_client.exception.errors.DenyError as error:
assert error.args[0] == '0x6985'

View File

@@ -0,0 +1,13 @@
from urllib import response
import boilerplate_client
import struct
def test_sign(cmd):
transaction = "dog"
response = cmd.simple_sign_tx(bip32_path="44'/60'/1'/0/0", transaction=transaction)
print(response)