Skip to content

Latest commit

 

History

History
283 lines (191 loc) · 22.7 KB

API.md

File metadata and controls

283 lines (191 loc) · 22.7 KB

trezor.js API

All the code examples are written with the assumption trezor.js is imported to the trezor variable. So, for example

 var trezor = require('trezor.js'); // if you are using the npm version

DeviceList

DeviceList is class that lists devices and emits events on any change.

var list = new trezor.DeviceList();

or

var list = new trezor.DeviceList(options);

DeviceList constructor takes care of initialization of transport layers. options is an optional object of key/value pair for configuring. Possible values are:

  • configUrl: if you want to use your own URL for config file
  • config: if you want to use your own config file. The value should be a hex string.
  • transport: if you want to provide your own Transport object (from trezor-link module)

The config should be available either on github, on Amazon AWS, or on wallet.trezor.io. The AWS URL is hardcoded and is used by default, so you don't have to worry about it if you don't want to.

DeviceList emits following events:

event parameters description
error e: Error Error on either initialization of DeviceList, or any other error.

If deviceList.transport is null, it means transport layer was not initialized and none of the layers are probably installed. You should probably offer user to install either extension or bridge in that case.
transport t: Transport When transport is successfully initialized.
connectUnacquired u: UnacquiredDevice When unacquired device is connected.
connect d: Device, previous: UnacquiredDevice When device is properly acquired.
If it was previously being used by someone else, previous is the unacquiredDevice, otherwise it's null.
disconnect d: Device When a device is disconnected.
disconnectUnacquired u: UnacquiredDevice When an unacquired device is disconnected.

DeviceList has following properties:

  • deviceList.devices - plain object, with values being the devices available and keys their paths
  • deviceList.unacquiredDevices - plain object, with values being unacquired devices and keys their paths
  • deviceList.transport - object, representing transport layer. You might use it in the following way:
    • if it's null, it means no transport layer was yet set up -- or the setup failed (nothing installed).
    • you can look at the deviceList.transport.version property, you can read the version as a string

DeviceList has following methods:

method parameters return type description
acquireFirstDevice rejectOnEmpty ?boolean Promise Easiest way of acquiring and getting the first device.
First parameter is optional - if true, rejects on empty list.

See multitasking for info on acquiring.

If you don't want to deal with acquired/unacquired/... devices, and you "just" want to use the first TREZOR, you can use the function deviceList.acquireFirstDevice, which will take first device currently connected, acquire it and never release it, and returns the Device and the Session objects. Other applications will then have to steal it to use it.

UnacquiredDevice

This class represents a device that cannot be acquired; see multitasking.

UnacquiredDevice emits following events:

event parameters description
connect d: Device This unacquired device was finally acquired and is now a Device.
disconnect This unacquired device was disconnected before it could be acquired.

UnacquiredDevice has following methods:

method parameters return type description
steal Promise Stops the action blocking acquiring and returns promise, resolving with the new Device.
See multitasking.

UnacquiredDevice has following properties:

  • unacquiredDevice.path - path uniquely defines device between more connected devices. It might or might not be unique over time; on some platform it changes, on others a given USB port always returns the same path.

Device

Device represents the actual TREZOR device. The most important method is run (or waitForSessionAndRun), which works in a following way:

device.run(function callback(session) {
    return session.signMessage([1,2], "message", "bitcoin");
});

device.run will first do acquire (see multitasking), then runs the callback function, and waits for the returned promise to resolve or reject. In both cases, it calls release.

It is important that the callback returns promise and the promise doesn't "hang", so the release is correctly called.

Note that the lenght of the action is not tied to the actual device actions. You can do more complex actions - for example, a complete account discovery - before the session is released.

Session object itself is described in the next section.

Device emits following events:

event parameters description
changedSessions isUsed: boolean
isUsedHere: boolean
Some change in sessions happened. Either release or acquire.
isUsed is true if the device is used (meaning session was acquired), either with this application or elsewhere.
isUsedHere is true if the device is used in this application.
disconnect This device was disconnected.
error e :Error Error
Can be anything from bad response to user pressing Cancel on device
send type: string, msg: Object (low level)
Message is being sent to Trezor with a given type
receive type: string, msg: Object (low level)
Message is being received from Trezor with a given type
button type: string User needs to confirm something on the device
pin type: string
callback: (error: Error, pin: string)=>void
The device asks for PIN.
You can either send PIN or send Error back to the device. You need to do one for the flow to continue.
PIN is randomized depending on device display; top right is "9", bottom left is "1". "123" means the bottom row on the display.
passphrase callback: (error: Error, passphrase: string)=>void The device asks for passhprase.
You can either send passphrase or send Error back to the device. You need to do one for the flow to continue.
word callback: (error: Error, word: string)=>void The device asks for word (on device recovery).
You can either send word or send Error back to the device. You need to do one for the flow to continue.

Device has following methods:

method parameters return type description
run callback: (Session)=>Promise<X>
[options: Object]
Promise<X> Runs an action on acquired session. Returns promise, that resolves with the same value as the callback result. With no options, the promise is rejected when there is another action running (see multitasking).

The optional option argument changes the behavior. If options.waiting is true, we wait for the current action to finish, if there is some. If options.aggressive is true, the current action is stopped.
waitForSessionAndRun callback: (Session)=>Promise<X> Promise<X> Equivalent to running run with options.waiting == true.
steal Promise See multitasking. Equivalent to running run with options.aggressive == true and an empty function.
isBootloader boolean Is device in a bootloader mode?
isInitialized boolean Is device initialized? (Meaning, does it have seed loaded)
getVersion string Get current firmware version
atLeast version: string boolean Compares this firmware version with another version
isUsed boolean Is anyone using this device? (Me or other app.) See multitasking.
isUsedHere boolean Am I anyone using this device?
isUsedElsewhere boolean Is anyone else using this device?

Device has following properties:

  • device.path - path uniquely defines device between more connected devices. It might or might not be unique over time; on some platform it changes, on others a given USB port always returns the same path.
  • device.features - basic information about a device
    • device.features.label - name of a device
    • device.features.pin_protection - is PIN protection turned on?
    • device.features.passphrase_protection - is PIN protection turned on?
    • device.features.device_id - a unique ID (is restarted only on device wipe)

Session

Session object represents one running session, between acquiring and releasing. All the "actual" calls are done here.

Session emits following events (note that they are also re-emitted by Device class):

event parameters description
error e :Error Error
Can be anything from bad response to user pressing Cancel on device
send type: string, msg: Object (low level)
Message is being sent to Trezor with a given type
receive type: string, msg: Object (low level)
Message is being received from Trezor with a given type
button type: string User needs to confirm something on the device
pin type: string
callback: (error: Error, pin: string)=>void
The device asks for PIN.
You can either send PIN or send Error back to the device. You need to do one for the flow to continue.
PIN is randomized depending on device display; top right is "9", bottom left is "1". "123" means the bottom row on the display.
passphrase callback: (error: Error, passphrase: string)=>void The device asks for passhprase.
You can either send passphrase or send Error back to the device. You need to do one for the flow to continue.
word callback: (error: Error, word: string)=>void The device asks for word (on device recovery).
You can either send word or send Error back to the device. You need to do one for the flow to continue.

Session has following methods:

method parameters return type description
typedCall type: string
resType: string
message: Object
Promise<Response> More low-level API. Sends message and returns message of given type.

Note that while this is low-level, it still doesn't return "intermediary" messages like asking for PIN and passhprase; those are emitted as events by Device and Session.
getEntropy size: number Promise<Response
<{ bytes: string }>>>
random data
getAddress path: Array<number>
coin: string
display: boolean
Promise<Response
<{ address: string }>>>
Gets address with a given path.
Coin is the name of coin ("bitcoin", "testnet", "litecoin",...)
if display is true, the address is displayed on TREZOR and user has to confirm.
ethereumGetAddress address_n: Array<number>
display: ?boolean
Promise<Response
<{ address: string, path: Array<number> }>>
Gets Ethereum address from a given path.
if display is true, the address is displayed on TREZOR and user has to confirm.
verifyAddress path: Array<number>
refAddress: string 
coin: string 
Promise<boolean> Gets address with the given path, displays it on display and compares to the refAddress.

Note: promise doesn't reject on different result, but resolves with false. It rejects on user not confirming on device.
getHDNode path: Array<number>
coin: string 
Promise<HDNode> Returns bitcoin.js HDNode with the given path. (No confirmation needed; it's public node.)
wipeDevice Promise Wipes the device (after user confirms).
resetDevice settings: {
strength: number,
passphrase_protection:
boolean,
pin_protection: boolean,
label: string
language: string
u2f_counter: number
skip_backup: boolean }
Promise Sets a new device. It has to be wiped first. It resolves after user confirms all the words on display.
loadDevice settings: {
strength: number,
passphrase_protection:
boolean,
pin_protection: boolean,
label: string,
mnemonic: string,
node HDNode,
payload: string }
network: string
Promise Loads a device with specific mnemonic and/or HD Node and/or xprv. Do not use this if you don't need to; use recoverDevice
Either mnemonic, node or payload have to be present. HDNode is an internal TREZOR structure, not bitcoin.js HD Node.
recoverDevice settings: {
word_count: number,
passphrase_protection:
boolean,
pin_protection: boolean,
label: string,
dry_run: boolean }
Promise Recovers device. If dry_run is enabled performs a dry run of the recovery process and displays result without resetting the device.
clearSession Promise Clears device "session".

"Session" has nothing to do with Session object or multitasking sessions; clearSession just clears currently remembered PIN and passphrase from device.
updateFirmware payload: string (hex) Promise Erases current firmware and loads a new one.
signMessage path: Array<number>
message: string
coin: string
segwit: boolean
Promise<
MessageResponse<
address:  string,
signature: string
>>
Signs a message.

Note: you have to pass the message encoded as HEX

Coin can be specified as string 'Bitcoin' or 'Litecoin': source
signEthMessage address_n: Array<number>
message: string
Promise<
MessageResponse<
address:  string,
signature: string
>>
Signs a message using an Ethereum private key.

Note: you have to pass the message encoded as HEX
verifyMessage address: string
signature: string
message: string
coin: string
Promise Verifies a signed message. Rejects on wrong signature.
verifyEthMessage address: string
signature: string
message: string
Promise Verifies a signed Ethereum message. Rejects on wrong signature.
signIdentity identity:{
proto:?string,
user:?string,
host:?string,
port:?string,
path:?string,
index:?number;}
challengeHidden: string
challengeVisual: string
Promise<
MessageResponse<
address: string,
public_key: string,
signature: string>>
Signs identity

TODO: write docs here
cipherKeyValue path: Array<number>
key: string
value: string
Buffer
encrypt: boolean
ask_on_encrypt: boolean
ask_on_decrypt: boolean
iv: [iv Buffer]
Promise<
Response<
{value: string
}>>
changeLabel label: string Promise Changes label.
togglePassphrase enable: boolean Promise Turns the passphrase on/off.
changeHomescreen picture: string (hex) Promise changes the homescreen.
signBjsTx info: TxInfo
refTx: Array<Transaction>
nodes: Array<HDNode>
coinName: string
network: bitcoin.Network
Promise<Transaction> Signs transaction after asking user on the device.
The name is signBjsTx for backwards compatibility, meaning "sign Bitcoin.js transaction", since it accepts and returns blockchain.js data structures.

The parameters are explained under this table.
signEthTx address_n: Array<number>
nonce: string
gas_price: string
gas_limit: string
to: string
value: string
data: ?string
chain_id: ?number
Promise<EthereumSignature> Signs Ethereum transaction after asking user on the device.
backupDevice Promise Initiates the display of recovery mnemonic, works only for devices with needs_backup true, sets needs_backup to false

Response<X> is an object with type {type: string, message: X}.

signBjsTx parameters

TxInfo is an object with:

  • inputs: Array<InputInfo>
  • outputs: Array<OutputInfo>

InputInfo is an object with:

  • hash: Buffer
    • note that, as in Bitcoin.js structures, the hash is reversed from the transaction IDs!
  • index: number
  • path: Array | null
    • present, if the input is from TREZOR

OutputInfo is an object with:

  • value: number
  • path: Array | null
  • address: string | null
    • either path or address must be present

refTxs are bitcoin.js transations, that are referenced in the inputs. You need to put all the transactions which are referenced in any of the inputs!

nodes is an array of Bitcoin.js HDNodes for addresses, with indexes being the index of the nodes. So, most often, [externalChainHDNode, changeChainHDNode].

coinName is "bitcoin", "litecoin", "dash", ...

network is network for Bitcoin.js. If it's not present, trezor.js tries to find it by coinName in bitcoin.js table.

Multitasking

More applications can, in general, work with the same TREZOR; however, they cannot perform actions with the device at the same time.

We use a "cooperative multitasking". Trezor.js does an "acquire" command to transport layer before any action, and after the action calls a "release". This is done transparently by trezor.js, so the app doesn't have to call the commands explicitly; however, it's important to keep the multitasking model in mind to understand various call results.

Multitasking flow

Flow

This image illustrates the flow. The left-right axis represents time; the two lines represent two applications, that both use trezor.js.

Ideal case

Ideal case

Connected device needs to do the acquire-release flow - before the Device object is even created (!) - in order to read some basic information about the device. Only then, the device event is being emitted by DeviceList.

This image illustrates a case, when there is no conflict between applications.

Waiting for initialization

Waiting for initialization

The first application can be in the middle of a long action (for example, a long transaction signing, or waiting for confirmation on a device) when the second application starts. In that case, Device object cannot be immediately created, since it cannot run acquire.

The Device object creation has to wait until the action is finished. DeviceList first emits unacquiredDevice, and only after the first application action finishes, the device event is emitted. Note that this happens automatically.

Stealing before initialization

Stealing before initialization

The first application does not have to finish the action (it might be stuck - the tab could be frozen, or perhaps there is a bug in the application, or perhaps user forgot about the tab). You can explicitly call steal() on the UnacquiredDevice object, which will stop the first application action and automatically acquire the device.

Note that you should only do steal when user explicitly asks for it - you don't want to stop other application from finishing its task, if it's not necessary.

Two concurrent acquires

Two concurrent acquires

Often, two acquires happen at the same time.

If you have two application open at the same time and one device is connected, both will try to call acquire at the same time.

Only one of those two acquires immediately succeed. The second application will first get unacquiredDevice event, and very quickly afterwards device event.

Running concurrent actions - default case

Two concurrent actions

If you try to use run() on Device while other application has acquired the device, the call will immediately fail and the run will return rejected promise, while the first action will continue running.

You can tell the status by calling isUsedElsewhere(), which returns boolean, see below.

Note that not even single application can run two actions at the same time. If you try to run() two actions at the same time, the second will fail. You can use isUsedHere() to detect the status of your application using the device.

Running concurrent actions - waiting

Two concurrent actions

If you use waitForSessionAndRun() on Device while other application has acquired the device, the call will wait until the other application called release and only then will happen.

Note that if the first application is frozen, the second application never gets to use the device.

Note that waitForSessionAndRun() also works within a single application.

Stealing

Stealing

Instead of waiting for session, you can actively stop the other action by using steal().

Stealing will not actually "give you" the session; however, it will stop the first action, allowing you to use run() immediately.

Strategies

Depending on how much complex do you want to make your application, you might use some of the following strategies.

If you want a simple application, you might want to either ignore unacquiredDevice events entirely, or, conversely, just immediately call steal() at the UnacquiredDevice objects. If you want a more robust application, you might want to offer the user the possibility to call steal() if the device stays unacquired for too much of a long time.

Similarly, if you want a simple application, you might want to either just call waitForSessionAndRun or, conversely, just call steal whenever the device is used and you want to run some action. If you want a more robust application, you might want to detect when the device is being used elsewhere and offer the user the possibility to call steal().