mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-25 17:09:44 +00:00
Initial commit
This commit is contained in:
parent
be876ae1d2
commit
99d892e759
0
__init__.py
Normal file
0
__init__.py
Normal file
0
bitkey_proto/__init__.py
Normal file
0
bitkey_proto/__init__.py
Normal file
147
bitkey_proto/bitkey.proto
Normal file
147
bitkey_proto/bitkey.proto
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
enum Algorithm {
|
||||||
|
BIP32 = 0;
|
||||||
|
ELECTRUM = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ScriptType {
|
||||||
|
PAYTOADDRESS = 0;
|
||||||
|
PAYTOSCRIPTHASH = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: None or Features
|
||||||
|
message Initialize {
|
||||||
|
}
|
||||||
|
|
||||||
|
message Features {
|
||||||
|
optional string version = 1;
|
||||||
|
optional bool otp = 2;
|
||||||
|
optional bool pin = 3;
|
||||||
|
optional bool spv = 4;
|
||||||
|
repeated Algorithm algo = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description: Test if another side is still alive.
|
||||||
|
// Response: None or Success
|
||||||
|
message Ping {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description: Response message for previous request with given id.
|
||||||
|
message Success {
|
||||||
|
optional string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description: Response message for previous request with given id.
|
||||||
|
message Failure {
|
||||||
|
optional int32 code = 1;
|
||||||
|
optional string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: UUID or Failure
|
||||||
|
message GetUUID {
|
||||||
|
}
|
||||||
|
|
||||||
|
message UUID {
|
||||||
|
required bytes UUID = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OtpRequest {
|
||||||
|
optional string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OtpAck {
|
||||||
|
required string otp = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OtpCancel {
|
||||||
|
}
|
||||||
|
|
||||||
|
message PinRequest {
|
||||||
|
optional string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PinAck {
|
||||||
|
required string pin = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PinCancel {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: OtpRequest, Entropy, Failure
|
||||||
|
message GetEntropy {
|
||||||
|
required uint32 size = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Entropy {
|
||||||
|
required bytes entropy = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: MasterPublicKey, Failure
|
||||||
|
message GetMasterPublicKey {
|
||||||
|
required Algorithm algo = 1 [default=BIP32];
|
||||||
|
}
|
||||||
|
|
||||||
|
message MasterPublicKey {
|
||||||
|
required bytes key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: Success, OtpRequest, Failure
|
||||||
|
message LoadDevice {
|
||||||
|
required string seed = 1;
|
||||||
|
optional bool otp = 2 [default=true];
|
||||||
|
optional string pin = 3;
|
||||||
|
optional bool spv = 4 [default=true];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: Success, OtpRequest, PinRequest, Failure
|
||||||
|
message ResetDevice {
|
||||||
|
}
|
||||||
|
|
||||||
|
message TxOutput {
|
||||||
|
required string address = 1;
|
||||||
|
repeated uint32 address_n = 2;
|
||||||
|
required uint64 amount = 3;
|
||||||
|
required ScriptType script_type = 4;
|
||||||
|
repeated bytes script_args = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: Success, SignedInput, Failure
|
||||||
|
message TxInput {
|
||||||
|
repeated uint32 address_n = 1;
|
||||||
|
required uint64 amount = 2;
|
||||||
|
required bytes prev_hash = 3;
|
||||||
|
required uint32 prev_index = 4;
|
||||||
|
optional bytes script_sig = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response: SignedTx, Success, OtpRequest, PinRequest, Failure
|
||||||
|
message SignTx {
|
||||||
|
required Algorithm algo = 1 [default=BIP32];
|
||||||
|
optional bool stream = 2; // enable streaming
|
||||||
|
required uint64 fee = 3;
|
||||||
|
repeated TxOutput outputs = 4;
|
||||||
|
repeated TxInput inputs = 5;
|
||||||
|
optional uint32 inputs_count = 6; // for streaming
|
||||||
|
optional bytes random = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SignedTx {
|
||||||
|
repeated bytes signature = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
inputs = [] # list of TxInput
|
||||||
|
for i in inputs:
|
||||||
|
for x in inputs:
|
||||||
|
send(x)
|
||||||
|
|
||||||
|
signature = send(SignInput(i))
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Response: SignedInput, Failure
|
||||||
|
message SignInput {
|
||||||
|
required TxInput input = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SignedInput {
|
||||||
|
required bytes signature = 1;
|
||||||
|
}
|
5
bitkey_proto/build.sh
Executable file
5
bitkey_proto/build.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd `dirname $0`
|
||||||
|
|
||||||
|
protoc --python_out=. bitkey.proto
|
51
bitkey_proto/mapping.py
Normal file
51
bitkey_proto/mapping.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import bitkey_pb2 as proto
|
||||||
|
|
||||||
|
map_type_to_class = {
|
||||||
|
0: proto.Initialize,
|
||||||
|
1: proto.Ping,
|
||||||
|
2: proto.Success,
|
||||||
|
3: proto.Failure,
|
||||||
|
4: proto.GetUUID,
|
||||||
|
5: proto.UUID,
|
||||||
|
6: proto.OtpRequest,
|
||||||
|
7: proto.OtpAck,
|
||||||
|
8: proto.OtpCancel,
|
||||||
|
9: proto.GetEntropy,
|
||||||
|
10: proto.Entropy,
|
||||||
|
11: proto.GetMasterPublicKey,
|
||||||
|
12: proto.MasterPublicKey,
|
||||||
|
13: proto.LoadDevice,
|
||||||
|
14: proto.ResetDevice,
|
||||||
|
15: proto.SignTx,
|
||||||
|
16: proto.SignedTx,
|
||||||
|
17: proto.Features,
|
||||||
|
18: proto.PinRequest,
|
||||||
|
19: proto.PinAck,
|
||||||
|
20: proto.PinCancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
map_class_to_type = {}
|
||||||
|
|
||||||
|
def get_type(msg):
|
||||||
|
return map_class_to_type[msg.__class__]
|
||||||
|
|
||||||
|
def get_class(t):
|
||||||
|
return map_type_to_class[t]
|
||||||
|
|
||||||
|
def build_index():
|
||||||
|
for k, v in map_type_to_class.items():
|
||||||
|
map_class_to_type[v] = k
|
||||||
|
|
||||||
|
def check_missing():
|
||||||
|
from google.protobuf import reflection
|
||||||
|
|
||||||
|
types = [ proto.__dict__[item] for item in dir(proto)
|
||||||
|
if issubclass(proto.__dict__[item].__class__, reflection.GeneratedProtocolMessageType) ]
|
||||||
|
|
||||||
|
missing = list(set(types) - set(map_type_to_class.values()))
|
||||||
|
|
||||||
|
if len(missing):
|
||||||
|
raise Exception("Following protobuf messages are not defined in mapping: %s" % missing)
|
||||||
|
|
||||||
|
check_missing()
|
||||||
|
build_index()
|
70
test.py
Executable file
70
test.py
Executable file
@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from transport_pipe import PipeTransport
|
||||||
|
from transport_serial import SerialTransport
|
||||||
|
from bitkey_proto import bitkey_pb2 as proto
|
||||||
|
|
||||||
|
def pprint(msg):
|
||||||
|
return "<%s>:\n%s" % (msg.__class__.__name__, msg)
|
||||||
|
|
||||||
|
def call(msg, tries=3):
|
||||||
|
print '----------------------'
|
||||||
|
print "Sending", pprint(msg)
|
||||||
|
d.write(msg)
|
||||||
|
resp = d.read()
|
||||||
|
|
||||||
|
if isinstance(resp, proto.OtpRequest):
|
||||||
|
if resp.message:
|
||||||
|
print "Message:", resp.message
|
||||||
|
otp = raw_input("OTP required: ")
|
||||||
|
d.write(proto.OtpAck(otp=otp))
|
||||||
|
resp = d.read()
|
||||||
|
|
||||||
|
if isinstance(resp, proto.PinRequest):
|
||||||
|
if resp.message:
|
||||||
|
print "Message:", resp.message
|
||||||
|
pin = raw_input("PIN required: ")
|
||||||
|
d.write(proto.PinAck(pin=pin))
|
||||||
|
resp = d.read()
|
||||||
|
|
||||||
|
if isinstance(resp, proto.Failure) and resp.code in (3, 6):
|
||||||
|
if tries <= 1 and resp.code == 3:
|
||||||
|
raise Exception("OTP is invalid, too many retries")
|
||||||
|
if tries <= 1 and resp.code == 6:
|
||||||
|
raise Exception("PIN is invalid, too many retries")
|
||||||
|
|
||||||
|
# Invalid OTP or PIN, try again
|
||||||
|
if resp.code == 3:
|
||||||
|
print "OTP is invalid, let's try again..."
|
||||||
|
elif resp.code == 6:
|
||||||
|
print "PIN is invalid, let's try again..."
|
||||||
|
|
||||||
|
return call(msg, tries-1)
|
||||||
|
|
||||||
|
if isinstance(resp, proto.Failure):
|
||||||
|
raise Exception(resp.code, resp.message)
|
||||||
|
|
||||||
|
print "Received", pprint(resp)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
d = PipeTransport('../../bitkey-python/device.socket', is_device=False)
|
||||||
|
#d = SerialTransport('../../bitkey-python/COM9')
|
||||||
|
|
||||||
|
#start = time.time()
|
||||||
|
|
||||||
|
#for x in range(1000):
|
||||||
|
|
||||||
|
call(proto.Initialize())
|
||||||
|
call(proto.Ping())
|
||||||
|
call(proto.GetUUID())
|
||||||
|
#call(proto.GetEntropy(size=10))
|
||||||
|
#call(proto.LoadDevice(seed='beyond neighbor scratch swirl embarrass doll cause also stick softly physical nice',
|
||||||
|
# otp=True, pin='1234', spv=True))
|
||||||
|
|
||||||
|
#call(proto.ResetDevice())
|
||||||
|
call(proto.GetMasterPublicKey(algo=proto.ELECTRUM))
|
||||||
|
#call(proto.ResetDevice())
|
||||||
|
|
||||||
|
#print 10000 / (time.time() - start)
|
62
transport.py
Normal file
62
transport.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import struct
|
||||||
|
from bitkey_proto import bitkey_pb2 as proto
|
||||||
|
from bitkey_proto import mapping
|
||||||
|
|
||||||
|
class Transport(object):
|
||||||
|
def __init__(self, device, *args, **kwargs):
|
||||||
|
self.device = device
|
||||||
|
self._open()
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def _close(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def _write(self, msg):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._close()
|
||||||
|
|
||||||
|
def write(self, msg):
|
||||||
|
ser = msg.SerializeToString()
|
||||||
|
header = struct.pack(">HL", mapping.get_type(msg), len(ser))
|
||||||
|
self._write("##%s%s" % (header, ser))
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
(msg_type, data) = self._read()
|
||||||
|
inst = mapping.get_class(msg_type)()
|
||||||
|
inst.ParseFromString(data)
|
||||||
|
return inst
|
||||||
|
|
||||||
|
def _read_headers(self, read_f):
|
||||||
|
# Try to read headers until some sane value are detected
|
||||||
|
is_ok = False
|
||||||
|
while not is_ok:
|
||||||
|
|
||||||
|
# Align cursor to the beginning of the header ("##")
|
||||||
|
c = read_f.read(1)
|
||||||
|
while c != '#':
|
||||||
|
if c == '':
|
||||||
|
# timeout
|
||||||
|
raise Exception("Timed out while waiting for the magic character")
|
||||||
|
print "Warning: Aligning to magic characters"
|
||||||
|
c = read_f.read(1)
|
||||||
|
|
||||||
|
if read_f.read(1) != "#":
|
||||||
|
# Second character must be # to be valid header
|
||||||
|
raise Exception("Second magic character is broken")
|
||||||
|
|
||||||
|
# Now we're most likely on the beginning of the header
|
||||||
|
try:
|
||||||
|
headerlen = struct.calcsize(">HL")
|
||||||
|
(msg_type, datalen) = struct.unpack(">HL", read_f.read(headerlen))
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
raise Exception("Cannot parse header length")
|
||||||
|
|
||||||
|
return (msg_type, datalen)
|
54
transport_pipe.py
Normal file
54
transport_pipe.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
'''TransportFake implements fake wire transport over local named pipe.
|
||||||
|
Use this transport for talking with bitkey simulator.'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from transport import Transport
|
||||||
|
|
||||||
|
class PipeTransport(Transport):
|
||||||
|
def __init__(self, device, is_device, *args, **kwargs):
|
||||||
|
self.is_device = is_device # Set True if act as device
|
||||||
|
|
||||||
|
super(PipeTransport, self).__init__(device, *args, **kwargs)
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
if self.is_device:
|
||||||
|
self.filename_read = self.device+'.to'
|
||||||
|
self.filename_write = self.device+'.from'
|
||||||
|
|
||||||
|
os.mkfifo(self.filename_read, 0600)
|
||||||
|
os.mkfifo(self.filename_write, 0600)
|
||||||
|
else:
|
||||||
|
self.filename_read = self.device+'.from'
|
||||||
|
self.filename_write = self.device+'.to'
|
||||||
|
|
||||||
|
if not os.path.exists(self.filename_write):
|
||||||
|
raise Exception("Not connected")
|
||||||
|
|
||||||
|
self.write_fd = os.open(self.filename_write, os.O_RDWR)#|os.O_NONBLOCK)
|
||||||
|
self.write_f = os.fdopen(self.write_fd, 'w+')
|
||||||
|
|
||||||
|
self.read_fd = os.open(self.filename_read, os.O_RDWR)#|os.O_NONBLOCK)
|
||||||
|
self.read_f = os.fdopen(self.read_fd, 'rb')
|
||||||
|
|
||||||
|
def _close(self):
|
||||||
|
self.read_f.close()
|
||||||
|
self.write_f.close()
|
||||||
|
os.unlink(self.filename_read)
|
||||||
|
os.unlink(self.filename_write)
|
||||||
|
|
||||||
|
def _write(self, msg):
|
||||||
|
try:
|
||||||
|
self.write_f.write(msg)
|
||||||
|
self.write_f.flush()
|
||||||
|
except OSError:
|
||||||
|
print "Error while writing to socket"
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
try:
|
||||||
|
(msg_type, datalen) = self._read_headers(self.read_f)
|
||||||
|
return (msg_type, self.read_f.read(datalen))
|
||||||
|
except IOError:
|
||||||
|
print "Failed to read from device"
|
||||||
|
raise
|
35
transport_serial.py
Normal file
35
transport_serial.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
'''SerialTransport implements wire transport over serial port.'''
|
||||||
|
|
||||||
|
# Local serial port loopback: socat PTY,link=COM8 PTY,link=COM9
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
from transport import Transport
|
||||||
|
|
||||||
|
class SerialTransport(Transport):
|
||||||
|
def __init__(self, device, *args, **kwargs):
|
||||||
|
self.serial = None
|
||||||
|
super(SerialTransport, self).__init__(device, *args, **kwargs)
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
self.serial = serial.Serial(self.device, 115200, timeout=10, writeTimeout=10)
|
||||||
|
|
||||||
|
def _close(self):
|
||||||
|
self.serial.close()
|
||||||
|
self.serial = None
|
||||||
|
|
||||||
|
def _write(self, msg):
|
||||||
|
try:
|
||||||
|
self.serial.write(msg)
|
||||||
|
self.serial.flush()
|
||||||
|
except serial.SerialException:
|
||||||
|
print "Error while writing to socket"
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
try:
|
||||||
|
(msg_type, datalen) = self._read_headers(self.serial)
|
||||||
|
return (msg_type, self.serial.read(datalen))
|
||||||
|
except serial.SerialException:
|
||||||
|
print "Failed to read from device"
|
||||||
|
raise
|
Loading…
Reference in New Issue
Block a user