It should be noted that this library as it stands right now is an early beta release. As such, there might be some unexpected results. Please join the community (see links below) and post issues, to ensure that the developers of the library can improve it.
Join the Discussion
If you want to get involved in the community, need help with getting setup, have any issues related with the library or just want to discuss Blockchain, Distributed Ledgers and IoT with other people, feel free to join our Slack. Slack You can also ask questions on our dedicated forum at: IOTA Forum.
npm install iotaledger/iota.flash.js
Flash Channels use a binary tree topology reduce the number of transactions to be attached. Usually each transfer would have to be attached to the tangle, Flash enables realtime streaming of tokens off-tangle.
A binary tree approach takes advantage of the fact that a signature can be used up-to 2 times while remaining reasonably secure. This lets us build a tree, where the terminating nodes (Leaves) are the individual transactions that will occur in the Flash channel. The path from the Leaf the Root acts to transfer the total value of the Root down to the current Leaf.
Given we are constructing a tree, we must determine the number of transactions in the channel when opening it. If we want a 15 transaction channel, we must have a tree with the depth of 5 (Math.ceil(log2(15)) + 1
).
As this uses log2, the larger the number of channel transactions the proportionally lower depth we need: ie. 1000 transactions require a depth of 10, where a 2000 transaction channel require a depth of 11.
A central tenant of IOTA's winternitz one time signature is that the address is not reused. In the case of Flash Channels, addresses can be reused one time. This degrades the security however given the realtime
nature of the channel there is a reasonable expectation that the channel will have progressed before an attack could take place. Please see a quick calculation relating to security here.
A common practice would be to chain Flash Channels in order to keep the 'channel' open. This is feesible 😉 given the lack of cost and the time required to open channels.
In Flash Channels there are two main type of data that is transferred. This is a proposed bundles and signatures related to that bundle. This the ONLY that should be transferred during the channel's operation. This allows a user to maintain their own Flash object (outlined below) and only mutates the state via the Flash library, preserving the integrity of the channel from their perspective.
Each proposed transfer replicates and then advances the whole state of the channel. It is imperative that each participant in the channel does a **diff **to check that the proposed transfer is not malicious and only contains the correct changes. This functionality is offered in the getDiff()
function within the library.
Example: A malicious user could craft a transfer that carries the correct values but change the remainder address, thus allowing them to take a large portion of the funds early in the channel's life.
Flash, at its core, is a set of rules governing how parties interact with the Multi-signature features of IOTA. Given this there is no limit to the number of users in a Flash Channel. This means you can have a consensus channel (all users agree) or a M of N channel (some users agree).
- Consensus would be good for logistically complex interactions with a large number of interrelated parties.
- M of N would work well for interactions where an arbiter could be present to help resolve disputes in the channel.
When users enter the channel they deposit funds into the newly generated deposit address (root of the binary tree). The amount they deposit is the amount of tokens they are able to spend in the channel. This amount also relates to trust as the amount they have deposit into this address is now under the control of all the parties that have signed the address.
It is common practice to deposit equal amounts into a channel ([100,100]) as both users now have an equal amount of tokens at stake. Therefore any misbehaviour or disagreements would result in both users being in an equally bad position. This would be useful for transacting with unknown users or channels with bidirectional payments.
For situations with reputation involved, a more one-sided channel can be created ([100,0]). This means that there is little incentive for the user with zero stake in the channel to be honest apart for a hit to their reputation. This is useful for one directional user (100) to machine (0) services where a business would otherwise have to hold large amounts of collateral in each of the Flash Channels open. ie EV Charging stations, an instant payment broker, or a streaming service.
For the Flash Channel to operate, a state object should be constructed to manage it. Below describes the Flash object that is recommended for use.
Each user has a copy of this object, however they should never transfer the object to each other. Each time a transfer occurs in the channel this object is updated by the applyTransfers
function which updates all the required values to the latest channel state.
Example Flash Object
{
signersCount: 2, // Number of signers in a channel
balance: 2000, // total channel balance
deposits: [1000,1000], // individual user deposits
settlementAddresses: ['ADKHAKXIW..', 'MAOODHQNA...'] // user's output addresses
depositAddress: 'AJDGAJDJS...', // Address at index 1 w/ checksum
remainderAddress: {...}, // Index 0 of the multisig addresses generated
root: {...}, // Index 1+ of the multisig addresses generated
outputs: {...},
transfers: [...] // History of transfers within the channel
}
- signersCount:
Int
Number of people partaking in the channel - balance:
Int
Index of the private key. - deposits:
Array
An array of the deposits of each channel user. - settlementAddresses:
Array
Array of user's settlement addresses for use when closing the channel. - depositAddress:
String
Tryte encoded address string. This is the address at index[1]
of the channel addresses - remainderAddress:
Object
Multisig object at address - root:
Object
Multisig object at index[1]
- outputs:
Object
Channel's outputs history is appended to this object - transfers:
Array
Channel's transfer history is appended to this array.
Generates the digest value of a key.
Input
multisig.getDigest(seed, index, security)
- seed:
String
Tryte encoded seed - index: 'Int' Index of the private key.
- security:
Int
Security level to be used for the private key
Returns
String
- digest represented in trytes.
Wraps the compose address related functions of iota.lib.js
. Returns a 81-tryte address from an Array of digests.
Note: The order of the digests in this function must be noted, as this order is required when signing the bundles.
Input
multisig.composeAddress([...digests])
- digests:
Array
Array of digests represented in trytes
Returns
String
- 81-tryte multisig address
Taking the root
of the tree, this function walks from the Leaf up to the root and finds the first node from the Leaf which is able to be used to sign a new transaction from. If an address can't be reused it increments a counter which is used to generate new addresses from.
Input
multisig.updateLeafToRoot(root)
- root:
Object
Representation of the current state of the Flash tree
Returns
Object
- An object containing the modifiedmultisigs
object to be used in the transactions and the number of new addressesgenerate
required to complete the transaction.
This function checks for sufficient funds and return a transfer array that will correctly transfer the right amount of IOTA, in relation to channel stake, into the users settlement address .
Input
transfer.prepare(
flash.settlementAddresses,
flash.deposit,
index,
transfers
)
- settlementAddresses:
array
the settlement addresses for each user - deposit:
array
the amount each user can still spend - index:
int
the index of the user used as an input - transfers:
array
the{value, address}
destination of the output bundle (excluding remainder)
Return
Array
- Transfer object
This method takes the transfers
object from the transfer.prepare
function and takes the flash object then constructs the required bundle for the next state in the channel.
Input
transfer.compose(
flash.balance,
flash.deposit,
flash.outputs,
multisig,
flash.remainderAddress,
flash.transfers,
newTansfers,
close
)
- balance:
Int
The total amount of iotas in the channel - deposit:
Array
The amount of iotas still available to each user to spend from - outputs:
String
The accrued outputs through the channel - multisig:
Object
history the leaf bundles - remainderAddress:
String
The remainder address of the Flash channel - history:
Array
Transfer history of the channel - newTransfers:
Array
transfers the array of outputs for the transfer - close:
Boolean
whether to use the minimum tree or not
Return
Array
Array of constructed bundle representing the latest state of the Flash channel. These bundles do not have signatures.
This takes the constructed bundles from compose
and then generates an array
of ordered signatures to be applied to the bundle.
Input
transfer.sign(
multisig,
seed,
bundles
)
- multisig:
Object
history the leaf bundles - seed:
String
Tryte encoded seed - bundles:
Array
Array of bundles that require signatures.
Return
Array
- An ordered array of signatures to be applied to the transfer bundle array.
This takes the bundles that have been generated by compose
and the signatures of one user and then applies them to the bundle. Use this function to apply all the signatures to the bundle.
The signatures must be applied in the order the address was generated. Otherwise the signatures will be invalid.
Note: You use the output of this function to apply the next set of signatures.
Input
transfer.appliedSignatures(
bundles,
signatures
)
- bundles:
Array
Array of bundles that require signatures. - signatures:
Array
Ordered set of one users signatures to be applied to the proposed bundle.
Return
Array
- An ordered array of bundles that have had the user's signatures applied.
This takes the channel history and latest bundles and runs checks to see where the changes are in the transfer.
Note: this is already called in the applyTransfers
function
Input
transfer.getDiff(
root,
remainder,
history,
bundles
)
- root:
Array
multisig object starting at the root of the tree - remainder:
Object
multisig object of the remainder address - history:
Array
Transfer history of the channel - bundles:
Array
Array of bundles for the proposed transfer
Return
Array
- An array of diffs per address
Input
transfer.applyTransfers(
flash.root,
flash.deposit,
flash.outputs,
flash.remainderAddress,
flash.transfers,
signedBundles
)
- root:
Object
Representation of the current state of the Flash tree - deposit:
Array
The amount of iotas still available to each user to spend from - outputs:
String
The accrued outputs through the channel - remainderAddress:
String
The remainder address of the Flash channel - history:
Array
Transfer history of the channel - signedBundles:
Array
Signed bundle
Return
This function mutates the flash objects that are passed to it. If there is an error in applying the transfers ie. Signatures aren't valid, the function will throw an error.
Used in place of the prepare
function when closing the channel. This is used to generate the closing transfers to each settlement address. It does this by correctly dividing the remaining channel balance amongst the channel's users.
Input
transfer.close(
flash.settlementAddresses,
flash.deposit
)
- settlementAddresses:
array
the settlement addresses for each user - deposit:
array
the amount each user can still spend
Return
Array
- Closing transfer object