Skip to content

Instantly share code, notes, and snippets.

@wolfmcnally
Last active April 9, 2020 03:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wolfmcnally/575ddd4bdef447aef1937143779a535a to your computer and use it in GitHub Desktop.
Save wolfmcnally/575ddd4bdef447aef1937143779a535a to your computer and use it in GitHub Desktop.
Seedtool Manual

🌱 Seedtool

Version 0.1.1
April 8, 2020

Copyright © 2020 by Blockchain Commons, LLC
Licensed under the "BSD-2-Clause Plus Patent License"


Introduction

seedtool is a command-line tool for creating and transforming cryptographic seeds of the sort commonly used by blockchain applications.

✅ NOTE: There is no mention below of password functionality for BIP39 and SLIP39 production. The current design allows for later inclusion of this feature via a --password option.

See README.md for installation and credits.

General Functionality

Supports --help, --usage, and --version.

$ seedtool --help
<help summary>

Generating Random Seeds

When run by itself, seedtool generates 16 random bytes (128 bits) and displays them as hex. This is the same as running seedtool --in random --out hex.

#
# Generate a 16-byte random seed.
#

$ seedtool
5b13fe9ac2778e728df31c7b1afd874f

#
# Same as above.
#

$ seedtool --in random --out hex
06799f71d16fad08ec5407d32d670147

An output format --out and count --count may be specified. Count may be in [1-64] and the default count is 16. For the hex output format, the count is the number of bytes generated. For other output formats, count is the number of "output units" (e.g., bits, cards, die rolls, etc.)

#
# Generate 16 random bytes.
#

$ seedtool --out hex
66a12c8a8c719dd3b2f951053910f849

#
# Generate 16 random bits.
#

$ seedtool --out bits
0110101010011010

#
# Generate 16 random playing cards.
#

$ seedtool --out cards
3d9sqcthkcad8c9hah5dqdtd5c8sjs7s

#
# Roll 16 random 6-sided dice.
#

$ seedtool --out dice
6565245451152635

#
# 6-sided dice, counting from zero.
#

$ seedtool --out base6
0534233032352210

#
# Generate 16 random digits in [0-9].
#

$ seedtool --out base10
9808565945186765

#
# Generate 5 random playing cards.
#

$ seedtool --out cards --count 5
9d6s2s9d8s

✅ NOTE: Playing cards may be duplicated; it is as if each card is drawn from a freshly shuffled deck.

Sequences of random integers may be generated. By default, --count is 16, --low is 0, and --high is 9.

low < high < 256

#
# Generate 16 random integers in [0-9].
#

$ seedtool --out ints
9 8 2 2 8 3 4 7 2 1 7 5 9 5 2 1

#
# Generate 8 random integers in [1-100].
#

$ seedtool --out ints --count 8 --low 1 --high 100
76 15 25 57 99 41 4 95

BIP39 can be used as an output format. The default --count is 16 bytes (128 bits, 12 words), but may be any multiple of 2 in [12,32].

#
# Generate a random 16-byte seed and display it as BIP39.
#

$ seedtool --out bip39
sorry pupil battle tortoise ceiling hurdle family market device primary language grit

#
# Generate a random 32-byte seed and display it as BIP39.
#

$ seedtool --out bip39 --count 32
mind knock evoke recycle payment snack pear party mean rubber open work rug trophy federal connect indicate security release three buzz buddy motion game

SLIP39 can be used as an output format. By default --count is 16 bytes (128 bits, 20 words), but may be any multiple of 2 in [16-32]. By default a single 1-of-1 share is generated. This is the same as running seedtool --out slip39 --group-threshold 1 --group 1-of-1.

#
# Generate a random 16-byte seed and display it as SLIP39.
#

$ seedtool --out slip39
activity away academic academic debut usher impact evidence worthy check vampire famous gums screw upstairs reunion practice welcome surface pink

#
# Generate a random 32-byte seed and display it as SLIP39.
#

$ seedtool --out slip39 --count 32
maiden corner academic academic always salon pancake software numb mixed spider artist smith total activity snake grumpy discuss pancake step apart elephant moisture strike review racism orange emission society lecture mansion forbid estate

n-of-m SLIP39 shares can be generated by specifing one or more groups.

#
# Generate a 16 byte seed and display it as SLIP39, one group,
# 2-of-3 shares required for seed recovery.
#

$ seedtool --out slip39 --group 2-of-3
health guest academic acid browser process marvel acid unkind usher entrance hobo punish quarter lawsuit aquatic advance rebuild satisfy remind
health guest academic agency capital salt leader video remember health nervous negative scroll spew survive patent expect capture should favorite
health guest academic always bolt inmate window treat elevator swing trend loyalty brother knife drift vampire forward counter emphasis preach

Multiple groups and a group threshold may be specified. If omitted, the default --group-threshold is 1.

✅ NOTE: The first two words of a set of SLIP39 shares are the same across all the shares. Each group within a set of SLIP39 shares has the same third word.

#
# Generate a 16 byte seed and display it as SLIP39, first group
# requires 2-of-3, second and third groups require 3-of-5, at least
# two groups must meet their threshold.
#

$ seedtool --out slip39 --group-threshold 2 --group 2-of-3 --group 3-of-5 --group 3-of-5
crucial enlarge acrobat leaf actress scatter brother cricket declare trial glance adult dress excuse cleanup coding behavior ting submit teacher
crucial enlarge acrobat lily aunt smirk strike plan oral terminal crunch mandate spark idle execute multiple exotic club learn rhythm
crucial enlarge acrobat lungs careful space necklace curious crunch zero mustang quiet gray detailed video vintage preach lunar hesitate upstairs
crucial enlarge beard learn desire pants display employer prayer square space shrimp warn expand buyer detailed likely safari therapy paces
crucial enlarge beard lips cultural symbolic scholar divorce flea drift exact join satisfy cowboy install decision mule ancient ruin network
crucial enlarge beard luxury blind mayor echo breathe froth chew payment payroll liberty excuse junction genius vitamins ordinary lift general
crucial enlarge beard march aquatic venture response group prize shadow dress away remember crowd axle gravity that finger phrase gross
crucial enlarge beard method dynamic legal shelter mineral switch sharp item scatter thumb frozen already slice family again pitch warmth
crucial enlarge ceramic learn custody ranked become kind petition burden axis royal freshman gasoline acrobat identify desire olympic depict marvel
crucial enlarge ceramic lips diminish necklace reject thunder increase aide senior wrist race papa depict national havoc grasp inform extra
crucial enlarge ceramic luxury anxiety platform marvel expect browser unfold shadow withdraw software trend river license guitar true beard identify
crucial enlarge ceramic march beaver lilac cleanup stadium smirk valid away salon dismiss corner material grief dictate bike firm pregnant
crucial enlarge ceramic method custody maximum campus earth ordinary twice adequate twin prayer cowboy relate window ruin raspy dominant equation

Generating Seeds from Provided Entropy

When the --in option is used, seedtool takes one or more arguments and uses them to construct the seed. If no arguments are given on the command line, it reads input from stdin and uses what it reads to construct the seed. In the examples below, the end of input to stdin is marked by ^D on its own line.

When the input format is hex, the construction is the identity function (passthrough.)

#
# Input a hex seed via stdin, receive the same seed back.
#

$ seedtool --in hex
3d1d142cd016cf8a393a1b477891c5e594fb7c9479b175a0db653067d6de0b17
^D
3d1d142cd016cf8a393a1b477891c5e594fb7c9479b175a0db653067d6de0b17

For the other input formats, each "unit" of the input (bit, digit, card, etc.) is converted to a byte and placed in an array. The SHA256 is then taken of the resulting array, yielding a deterministic seed. This seed is then used to generate a cryptographic seed of count bytes.

#
# Start by generating 16 random bits. You could do this by flipping
# a coin 16 times.
#

$ seedtool --out bits
1110110001110111

#
# Construct a 16-byte seed from those bits, providing them on
# the command line.
#

$ seedtool --in bits 1110110001110111
8d933e43b1bc8f2e3fc27adc98ad4534

#
# Construct the same seed from those bits, providing them via stdin.
#

$ seedtool --in bits
1110110001110111
^D
8d933e43b1bc8f2e3fc27adc98ad4534
#
# Start by generating 16 random playing cards. You could do this
# by drawing cards from an actual deck, shuffling between each draw.
#

$ seedtool --out cards
6c9s8c7c9c4cah6c2sjs7d5c2s4c4dqs

#
# Construct a 16-byte seed from those playing cards, providing
# them on the command line.
#

$ seedtool --in cards 6c9s8c7c9c4cah6c2sjs7d5c2s4c4dqs
7df301924511326d7350be14c9e7176d

#
# Construct a longer 32-byte seed from those same playing cards.
#

$ seedtool --count 32 --in cards 6c9s8c7c9c4cah6c2sjs7d5c2s4c4dqs
7df301924511326d7350be14c9e7176d98e945f9ad0ed034726ad4ee0de59c25
#
# Construct a 16-byte seed from any number of die rolls. You could
# do this by rolling actual dice.
#

$ seedtool --in dice 3343462611234633
77ae0de807d60367311eb040c70690d2

#
# Construct a 16-byte seed from any number of digits in [0-5].
#

$ seedtool --in base6 3242235101442242
51621269b3a91fe6482ceb7779f0d1d1

#
# Construct a 16-byte seed from any number of digits in [0-9].
#

$ seedtool --in base10 3190125
a0ca21e20db54b4df7479737c145f6db

#
# Construct a 16-byte seed from any number of integers in [0-255].
#
$ seedtool --in ints 71 22 95 6 23 65 27 10 67 16
a38385ba7a67b7f5882b37f75b43c2df

Output of one call to seedtool can be piped into another.

#
# Roll 16 dice and create a 32-byte seed from them.
#

$ seedtool --out dice | seedtool --in dice --count 32
7bdf68608e30da4b9ec48af0cb48f2601b41d1bcc8859b4f625d6c0470f3a6dd

#
# Roll 16 dice saving them to a file, and create a 16-byte seed
# from them.
#

$ seedtool --out dice | tee dice.asc | seedtool --in dice
c13be193c8e3451a20b75e8dc0f69284
$ cat dice.asc 
4435442555226432

If a smaller or larger seed is desired, the --count option specifies how many bytes it contains.

#
# Create an 8-byte seed from any number of bits.
#

$ seedtool --in bits --count 8 0111100011000011
1a4783f9e4e8eb68

#
# Create a 32-byte seed from any number of bits.
#

$ seedtool --in bits --count 32 0111100011000011
1a4783f9e4e8eb6862cbd34acfe4f79ee8ee4e0e6f5726e589a36c7e8bf8a547

#
# Create a 20-byte seed from any number of playing cards.
#

$ seedtool --in cards --count 20 6c2c3hthacts6d4hkhtd2d7c6c3sqs6h
731e0a4c76189b2b55f4c705ccbb0105d3ee72c0

bip39 and slip39 output formats can be combined with the random (default) input format. If the --count N option is used with the hex input format, it results in a seed of N bytes being generated and used.

#
# Create a random 16-byte seed and display it as BIP39.
#

$ seedtool --out bip39
monitor place true skirt uncover scissors tower alley fame grunt sun outer

#
# Create a random 32-byte seed and display it as a single
# SLIP39 share.
#

$ seedtool --in random --out slip39 --count 32
pumps guest academic academic analysis election admit harvest very webcam acquire answer primary viral venture declare have short bucket pickup pistol squeeze script racism western alarm depend depart lilac zero capacity capture warn

bip39 and slip39 output formats can be combined with the hex input format. The --count option is not allowed and the whole hex seed is used. For bip39 the seed must be 12-32 bytes and even. For slip39 the seed must be 16-32 bytes and even.

#
# Start by generating a random 16-byte seed.
#

$ seedtool --out hex
8a3796240f6a9606a577c887f2e5c83a

#
# Input the seed above and display it as BIP39.
#

$ seedtool --in hex --out bip39 8a3796240f6a9606a577c887f2e5c83a
mechanic royal math burst practice addict noise weekend margin now improve invest

#
# The --count option is not available when providing the
# seed yourself.
#

$ seedtool --count 12 --in hex --out bip39 8a3796240f6a9606a577c887f2e5c83a
seedtool: The --count option is not available for hex input.

#
# The seed you provide must conform to the output format constraints.
#

$ seedtool --in hex --out bip39 8a3796240f6a
seedtool: Invalid seed length for BIP39. Must be in [12-32] and even.

bip39 can be used as an input format, in which case the original seed is recovered. The BIP39 mnemonic sequence may be passed as one or more arguments on the command line, or entered via stdin.

#
# Recover a seed from a BIP39 mnemonic sequence, providing each
# word as a separate argument on the command line.
#

$ seedtool --in bip39 mechanic royal math burst practice addict noise weekend margin now improve invest
8a3796240f6a9606a577c887f2e5c83a

#
# Recover from the same BIP39 mnemonic sequence, providing all
# words as a single (quoted) argument on the command line.
#

$ seedtool --in bip39 "mechanic royal math burst practice addict noise weekend margin now improve invest"
8a3796240f6a9606a577c887f2e5c83a

#
# Recover from the same BIP39 mnemonic sequence, providing all
# the words via stdin.
#

$ seedtool --in bip39
mechanic royal math burst practice addict noise weekend margin now improve invest
^D
8a3796240f6a9606a577c887f2e5c83a

slip39 can be used as an input format, in which case the original seed is recovered. The SLIP39 shares may be passed on the command line or entered via stdin. If passed on the command line, the shares must each be a single argument (i.e., quoted). If passed via stdin, each share must appear by itself on one line.

#
# Recover a seed from a SLIP39 share, providing the entire share
# as a single (quoted) argument on the command line.
#

$ seedtool --in slip39 "activity away academic academic debut usher impact evidence worthy check vampire famous gums screw upstairs reunion practice welcome surface pink"
6c641757596dfefb95863b13f5f8a247

#
# Recover from the same SLIP39 share, providing the share via stdin.
#

$ seedtool --in slip39
activity away academic academic debut usher impact evidence worthy check vampire famous gums screw upstairs reunion practice welcome surface pink
^D
6c641757596dfefb95863b13f5f8a247

#
# The same SLIP39 share is used as above, but since it is not
# quoted, seedtool tries to use each word as a single share and
# fails to recover the seed.
#

$ seedtool --in slip39 activity away academic academic debut usher impact evidence worthy check vampire famous gums screw upstairs reunion practice welcome surface pink
seedtool: Invalid SLIP39 shares.

In this example, 2 shares of a 2-of-3 split are entered on the command line (each separately quoted) and via stdin (each on its own separate line.)

#
# Generate a random 16-byte seed and display it as SLIP39 in a
# single group requiring 2-of-3 shares.
#

$ seedtool --out slip39 --group 2-of-3
cowboy leader academic acid critical employer aspect stick result much camera favorite smirk domestic staff phantom fake result slush loud
cowboy leader academic agency biology imply grasp buyer strategy founder alive hybrid cultural forget maiden playoff analysis home moment snapshot
cowboy leader academic always benefit crisis quick tendency decision being curious priority evoke welfare hour burning champion tracks maiden salary

#
# Recover from the first and third shares above, providing each
# separately quoted on the command line.
#

$ seedtool --in slip39 "cowboy leader academic acid critical employer aspect stick result much camera favorite smirk domestic staff phantom fake result slush loud" "cowboy leader academic always benefit crisis quick tendency decision being curious priority evoke welfare hour burning champion tracks maiden salary"
747008a3b462468c5c3e62bf692349d0

#
# Recover from the first and third shares above, providing each
# on its own separate line via stdin.
#

$ seedtool --in slip39
cowboy leader academic acid critical employer aspect stick result much camera favorite smirk domestic staff phantom fake result slush loud
cowboy leader academic always benefit crisis quick tendency decision being curious priority evoke welfare hour burning champion tracks maiden salary
^D
747008a3b462468c5c3e62bf692349d0

Deterministic Randomness

When using the --in random (default input format) option, seedtool uses a cryptographically-strong random number generator provided by the operating system. The same random number generator is also used when constructing SLIP39 shares, so the same entropy input will yield different shares each time.

#
# Generate entropy. You could do this by rolling real dice.
#

$ seedtool --out dice
5343553122555345

#
# Construct seed from entropy.
#

$ seedtool --in dice 5343553122555345
5d1c30bbc6f3cfd070067b63c851ffe7

#
# Construct SLIP39 share from seed.
#

$ seedtool --in hex --out slip39 5d1c30bbc6f3cfd070067b63c851ffe7
edge enlarge academic academic body necklace surprise resident burden taxi painting slim research teammate peasant ivory weapon gesture voice flexible

#
# Recover the seed from the SLIP39 share.
#

$ seedtool --in slip39 "edge enlarge academic academic body necklace surprise resident burden taxi painting slim research teammate peasant ivory weapon gesture voice flexible"
5d1c30bbc6f3cfd070067b63c851ffe7

#
# Again construct SLIP39 share from the same seed. Notice the
# share is different than last time.
#

$ seedtool --in hex --out slip39 5d1c30bbc6f3cfd070067b63c851ffe7
result leader academic academic apart length alcohol adult patrol military counter enjoy animal standard club facility belong rumor problem answer

#
# Recover the second share and notice that the seed is the same.
#

$ seedtool --in slip39 "result leader academic academic apart length alcohol adult patrol military counter enjoy animal standard club facility belong rumor problem answer"
5d1c30bbc6f3cfd070067b63c851ffe7

Seedtool also provides the --deterministic S option, which takes a string S, produces the SHA256 hash of that string, and then uses that to seed it's own cryptography-quality random number generator it uses for the rest of its run. This means that seeds generated by seedtool with the same --deterministic input will yield the same results.

#
# Generate a 16-byte seed using deterministic randomness.
#

$ seedtool --deterministic test
d551108c3e7831532beded6b29438683

#
# Notice that using the same value for --deterministic results
# in the same output.
#

$ seedtool --deterministic test
d551108c3e7831532beded6b29438683

#
# Using a different value for --deterministic results in
# different output.
#

$ seedtool --deterministic test2
a97770028023ece0f9307bf867b4c740

In this example, the same entropy is used twice in producing a SLIP39 share, which would normally result in two different shares being produced. But by providing --deterministic FOOBAR, the same results are produced.

#
# Generate 16 die rolls using deterministic randomness.
#

$ seedtool --deterministic FOOBAR --out dice
1533324122434244

#
# Use our die rolls to generate a 16-byte seed.
#

$ seedtool --in dice 1533324122434244
5cd271b50b98a869da1c26a526e1d3a8

#
# Display our seed as a SLIP39 2-of-3 shares, using
# deterministic randomness.
#

$ seedtool --deterministic FOOBAR --in hex --out slip39 --group 2-of-3 5cd271b50b98a869da1c26a526e1d3a8
response lunch academic acid breathe ocean mixture traffic object sheriff rapids victim froth testify retreat patent prize human usual hobo
response lunch academic agency activity fiber escape research earth pipeline prevent prepare activity erode grumpy problem justice vampire tension custody
response lunch academic always crisis cards chemical expect muscle anxiety training thunder climate plan geology bulb scatter teacher gravity guilt

#
# Do all of the above on a single line, and note that because
# of deterministic randomness, the same final output is produced.
#

$ seedtool --deterministic FOOBAR --out dice | seedtool --in dice | seedtool --deterministic FOOBAR --in hex --out slip39 --group 2-of-3
response lunch academic acid breathe ocean mixture traffic object sheriff rapids victim froth testify retreat patent prize human usual hobo
response lunch academic agency activity fiber escape research earth pipeline prevent prepare activity erode grumpy problem justice vampire tension custody
response lunch academic always crisis cards chemical expect muscle anxiety training thunder climate plan geology bulb scatter teacher gravity guilt

Compatibility

  • seedtool has been tested to give the same results as Ian Coleman's BIP39 tool for the following input methods: base6, base10, bits, and dice.
  • hex as an input method is already compatible if used with his tool in "raw entropy" mode.
  • cards is not currently compatible.

The example below can be replicated using Coleman's tool and selecting the following options:

  • Show entropy
  • Base: 10
  • Entropy: 123456
  • Mnemonic length: 12 words
$ seedtool --in base10 123456 | seedtool --in hex --out bip39
mirror reject rookie talk pudding throw happy era myth already payment owner

Version History

0.1.1, 4/8/2020

  • Added compatibility with Ian Coleman's BIP39 tool for several input formats:
    • base6, base10, bits, and dice are now compatible.
    • hex as an input method is already compatible if used with his tool in "raw entropy" mode.
    • cards is not compatible as his algorithm is complex and possibly not worth spending the time on.
  • Updated MANUAL.md examples to remain accurate.
  • Added a section on Compatibility with other tools to MANUAL.md.

0.1.0, 4/8/2020

  • First test release.
@ksedgwic
Copy link

ksedgwic commented Apr 7, 2020

Default Sizes

I think the default (encouraged) sizes for BIP-39 should be 12 words and
SLIP-39 shares should be 20 words.

The only arguments I'm aware of for the larger sizes are:

  1. They are much more secure. I think this is a really bad deal.
    128 bits of entropy are completely adequte and the additional cost
    of recording, transcribing etc of the larger sizes have a terrible
    cost, especially with the low-tech methods (stamping, etching, writing ...)
    we otherwise use for cold storage security.

  2. They help secure the recovery procedure of the Trezor One. The
    Trezor Wiki describes it well:

To mitigate any risks of the seed being compromised by malicious
key-logging software, the Trezor One device instructs the user to
enter the individual words in random order - instead of entering all
words in sequence from the first word to the last one. Computing a
valid seed out of 24 random seed words is almost impossibly
difficult (i.e., this never happens).

To add more strength (randomness) when recovering a 12-word seed on
Trezor One, there are 12 fake dummy words introduced by the device
and mixed in the pool with the real words. The user inputs the real
words mixed with the fake ones, all randomly shuffled, and the
device itself sorts them out.

The recovery process using Trezor Model T is limited to the
"on-device" input, meaning that the words never touch a potentially
compromised environment and always stay safe as you type them in
using the touchscreen. In this case, 12 words are sufficient. The
128-bit entropy (randomness) provided by 12 words is widely
considered to be plenty secure.

I don't think either of these justifications applies in the case of this tool.

Default Truncation of the Seed Should be 16 Bytes

The proposal is to use all 32 bytes of the SHA256 hash as a seed:

The SHA256 is then taken of the resulting array, yielding a
deterministic seed.

If a smaller seed is desired, the --count option can specify that
the SHA256 be truncated to a fewer number of bytes.

I am aware of 3 similar tools:

All three use the first 16 bytes of the SHA256 hash as the seed. All
three generate the same mnemonic passphrase for the dice roll of
"123456".

PyMultiWallet:

[user@arduino pymultiwallet] git:(master)$ mw --entropy --generate
Enter entropy string followed by a \n. No entropy is added, make sure you provide enough.
: 123456
mirror reject rookie talk pudding throw happy era myth already payment owner

Mnemonic Code Converter settings: "Show entropy details", Entropy="123456", "Base 10", "12 words"

MCC and Seedtool

@maaku
Copy link

maaku commented Apr 7, 2020

128-bit private keys are a reasonable default, as is truncation to 16 bytes, so long as both can be overridden when needed.

There are obscure defense-in-depth reasons to suggest that, for example, a truncated output should use a different or tagged hash function so that truncation isn't just literally dropping bytes from the output as these other tools do. But not changing the hash doesn't create a vulnerability by itself, and at this point the ship has sailed: there is more benefit to unifying behavior with other deployed infrastructure than going our own way.

@ChristopherA
Copy link

Do you have thoughts on future for -cli for deriving keys? entering path, etc. Any good examples from libbitcoin's command line tool? Maybe a section on future thoughts and put the --password option there.

-- Christopher Allen

@ChristopherA
Copy link

One of our longer-term goals is to use this with a future edition Bitcoin from the Command Line course (which will be moving to Blockchain Commons), as bitcoin-core doesn't support BIP39. In particular after section where we introduce JQ https://github.com/ChristopherA/Learning-Bitcoin-from-the-Command-Line/blob/master/04_2__Interlude_Using_JQ.md we'll want to use bitcoin-core's named parameters and jq along with seedtool-cli to show how you can use BIP39 with bitcoin-core.

The question I have for you is if there are some minor changes in the input (like option for named parameters) and output (json), that we should plan for in a "future thoughts" section.

-- Christopher Allen

@ChristopherA
Copy link

For future, it might be nice to have an option for output that was a numbered list -o list and for a json list -o json

@maaku
Copy link

maaku commented Apr 8, 2020

Feed the output into cat -n.

Which leads to a pet peeve of mine: command line utilities which output JSON. Although JQ makes things bearable, you still miss out on the entire ecosystem of more powerful UNIX text manipulation tools. I would keep the formats for both input and output as simple as possible.

@ChristopherA
Copy link

I don’t want JSON to be the only or default option, but sometimes it is very useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment