cmd: add address generation

here a new subcommand is added: `address`.

the idea here is to provide an example of how address-related
funcionality from `pkg/monero` can be used.

at the moment, the only available action is "generate", which
instantiates a new seed based on `crypto/rand` and then displaying on
stdout the result

	WARNING: DO NOT USE THIS FOR ANYTHING MEANINGFUL.
	you've been advised.

example:

	$ monero address generate

	Mnemonic:  dawn      repent   towel    taxi
		   cucumber  muzzle   romance  awesome
		   losing    yeti     dogs     biplane
		   foyer     hotel    tattoo   dilute
		   gearbox   later    afloat   purged
		   software  ashtray  cell     dangerous
		   biplane

	Primary Address:        49MYaXwy8K177bw9i1bBDvPuM...
	Private Spend Key:      455f1c286ff8db620e61ca6c6...
	Private View Key:       b5d26a403c6cec29c3ecc8d2f...
	Public Spend Key:       cc06a0f6e6c6b0248d5e2c3fd...
	Public View Key:        bcb8d3dc372efb9071c120b72...

	(output truncated to fit the commit message).

Signed-off-by: Ciro S. Costa <utxobr@protonmail.com>
This commit is contained in:
Ciro S. Costa 2021-08-25 18:26:09 -04:00
parent ae4d6f3a92
commit 74a9dfbdca
11 changed files with 2119 additions and 15 deletions

View file

@ -317,6 +317,7 @@ Big thanks to the Monero community and other projects around cryptonote:
- https://github.com/cdiv1e12/py-levin
- https://github.com/cryptonotefoundation/cryptonote
- https://github.com/LeTurt/turtlegod
- https://github.com/monero-ecosystem/vanity-monero
## Donate

View file

@ -0,0 +1,10 @@
package address
import (
"github.com/spf13/cobra"
)
var RootCommand = &cobra.Command{
Use: "address",
Short: "address-related utils",
}

View file

@ -0,0 +1,144 @@
package address
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
"github.com/cirocosta/go-monero/pkg/monero"
)
type generateCommand struct {
passphrase string
networkName string
}
func (c *generateCommand) Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "generate",
Short: "create a new single-key seed",
RunE: c.RunE,
}
cmd.Flags().StringVar(&c.passphrase, "passphrase", "",
"string to encode and use as offset for the seed")
cmd.Flags().StringVar(&c.networkName, "network", "mainnet",
"network that the addresses should be used for "+
c.networkOptions())
return cmd
}
func (c *generateCommand) RunE(_ *cobra.Command, _ []string) error {
privateKey, err := c.privateKey()
if err != nil {
return fmt.Errorf("private key: %w", err)
}
network, err := c.network()
if err != nil {
return fmt.Errorf("network: %w", err)
}
c.pretty(monero.NewSeed(privateKey, monero.WithNetwork(network)))
return nil
}
func (c *generateCommand) pretty(seed *monero.Seed) {
c.prettyMnemonic(seed)
c.prettyKeys(seed)
}
func (c *generateCommand) prettyKeys(seed *monero.Seed) {
table := display.NewTable()
defer fmt.Println(table)
table.AddRow("Primary Address:", seed.PrimaryAddress())
table.AddRow("Private Spend Key:",
hex.EncodeToString(seed.PrivateSpendKey()))
table.AddRow("Private View Key:",
hex.EncodeToString(seed.PrivateViewKey()))
table.AddRow("Public Spend Key:",
hex.EncodeToString(seed.PublicSpendKey()))
table.AddRow("Public View Key:",
hex.EncodeToString(seed.PublicViewKey()))
}
func (c *generateCommand) prettyMnemonic(seed *monero.Seed) {
table := display.NewTable()
defer fmt.Println(table)
table.Separator = " "
mnemonic := seed.Mnemonic()
table.AddRow(c.row("Mnemonic:", mnemonic[0:4]...)...)
table.AddRow(c.row("", mnemonic[4:8]...)...)
table.AddRow(c.row("", mnemonic[8:12]...)...)
table.AddRow(c.row("", mnemonic[12:16]...)...)
table.AddRow(c.row("", mnemonic[16:20]...)...)
table.AddRow(c.row("", mnemonic[20:24]...)...)
table.AddRow(c.row("", mnemonic[24])...)
table.AddRow("")
}
func (c *generateCommand) row(key string, values ...string) []interface{} {
res := []interface{}{key}
for _, v := range values {
res = append(res, v)
}
return res
}
func (c *generateCommand) privateKey() ([]byte, error) {
v := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, v)
if err != nil {
return nil, fmt.Errorf("read full: %w", err)
}
return v, nil
}
func (c *generateCommand) networkOptions() string {
strs := []string{}
for _, network := range []monero.Network{
monero.NetworkMainnet,
monero.NetworkTestnet,
monero.NetworkStagenet,
monero.NetworkFakechain,
} {
strs = append(strs, string(network))
}
return "(" + strings.Join(strs, ",") + ")"
}
func (c *generateCommand) network() (monero.Network, error) {
switch c.networkName {
case string(monero.NetworkMainnet):
return monero.NetworkMainnet, nil
case string(monero.NetworkTestnet):
return monero.NetworkTestnet, nil
case string(monero.NetworkStagenet):
return monero.NetworkStagenet, nil
case string(monero.NetworkFakechain):
return monero.NetworkFakechain, nil
}
err := fmt.Errorf("unknown network %s", c.network)
return monero.NetworkFakechain, err
}
func init() {
RootCommand.AddCommand((&generateCommand{}).Cmd())
}

View file

@ -6,6 +6,7 @@ import (
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/commands/address"
"github.com/cirocosta/go-monero/cmd/monero/commands/daemon"
"github.com/cirocosta/go-monero/cmd/monero/commands/p2p"
"github.com/cirocosta/go-monero/cmd/monero/commands/wallet"
@ -34,6 +35,7 @@ func init() {
rootCmd.AddCommand(daemon.RootCommand)
rootCmd.AddCommand(wallet.RootCommand)
rootCmd.AddCommand(p2p.RootCommand)
rootCmd.AddCommand(address.RootCommand)
rootCmd.AddCommand(versionCmd)
}

15
go.mod
View file

@ -4,20 +4,23 @@ go 1.17
require (
github.com/dustin/go-humanize v1.0.0
github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b // indirect
github.com/go-zeromq/zmq4 v0.13.0
github.com/golangci/golangci-lint v1.42.0
github.com/gosuri/uitable v0.0.4
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/paxos-bankchain/moneroutil v0.0.0-20170611151923-33d7e0c11a62
github.com/sclevine/spec v1.4.0
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/net v0.0.0-20210825183410-e898025ed96a
)
require (
4d63.com/gochecknoglobals v0.0.0-20210416044342-fb0abda3d9aa // indirect
github.com/Antonboom/errname v0.1.3 // indirect
github.com/Antonboom/errname v0.1.4 // indirect
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
@ -28,7 +31,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.0 // indirect
github.com/bombsimon/wsl/v3 v3.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/charithe/durationcheck v0.0.8 // indirect
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
github.com/daixiang0/gci v0.2.9 // indirect
@ -38,7 +41,7 @@ require (
github.com/ettle/strcase v0.1.1 // indirect
github.com/fatih/color v1.12.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/fsnotify/fsnotify v1.5.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fzipp/gocyclo v0.3.1 // indirect
github.com/go-critic/go-critic v0.5.7 // indirect
github.com/go-toolsmith/astcast v1.0.0 // indirect
@ -128,7 +131,7 @@ require (
github.com/stretchr/objx v0.3.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b // indirect
github.com/tetafro/godot v1.4.8 // indirect
github.com/tetafro/godot v1.4.9 // indirect
github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect
github.com/tomarrell/wrapcheck/v2 v2.3.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.4.0 // indirect
@ -138,7 +141,7 @@ require (
github.com/yeya24/promlinter v0.1.0 // indirect
golang.org/x/mod v0.5.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 // indirect
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect

28
go.sum
View file

@ -45,8 +45,9 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Antonboom/errname v0.1.3 h1:qKV8gSzPzBqrG/q0dgraZXJCymWt6KuD9+Y7K7xtzN8=
github.com/Antonboom/errname v0.1.3/go.mod h1:jRXo3m0E0EuCnK3wbsSVH3X55Z4iTDLl6ZfCxwFj4TM=
github.com/Antonboom/errname v0.1.4 h1:lGSlI42Gm4bI1e+IITtXJXvxFM8N7naWimVFKcb0McY=
github.com/Antonboom/errname v0.1.4/go.mod h1:jRXo3m0E0EuCnK3wbsSVH3X55Z4iTDLl6ZfCxwFj4TM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@ -99,8 +100,9 @@ github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charithe/durationcheck v0.0.8 h1:cnZrThioNW9gSV5JsRIXmkyHUbcDH7Y9hkzFDVc9/j0=
github.com/charithe/durationcheck v0.0.8/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg=
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af h1:spmv8nSH9h5oCQf40jt/ufBCt9j0/58u4G+rkeMqXGI=
@ -143,6 +145,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b h1:BMyjwV6Fal/Ffphi4dJfulSxMeDl0xFS2vs5QLr6rsI=
github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b/go.mod h1:fnviDXB7GJWiSUI9thIXmk9QKM8Rhj1JV/LcMRzkiVA=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -164,8 +168,8 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.0 h1:NO5hkcB+srp1x6QmwvNZLeaOgbM8cmBTN32THzjvu2k=
github.com/fsnotify/fsnotify v1.5.0/go.mod h1:BX0DCEr5pT4jm2CnQdVP1lFV521fcCNcyEeNp4DQQDk=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
github.com/fzipp/gocyclo v0.3.1 h1:A9UeX3HJSXTBzvHzhqoYVuE0eAhe+aM8XBCCwsPMZOc=
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
@ -563,6 +567,8 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/paxos-bankchain/moneroutil v0.0.0-20170611151923-33d7e0c11a62 h1:QYHPf1LKI9NldUrn3Elc6YQwJPJEbFgO+4vQCShYLTI=
github.com/paxos-bankchain/moneroutil v0.0.0-20170611151923-33d7e0c11a62/go.mod h1:luERRHUHsQsJNogV9bXoGW6dRFS9qjv0121Z6Gg/iuk=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
@ -714,8 +720,9 @@ github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
github.com/tetafro/godot v1.4.8 h1:rhuUH+tBrx24yVAr6Ox3/UxcsiUPPJcGhinfLdbdew0=
github.com/tetafro/godot v1.4.8/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8=
github.com/tetafro/godot v1.4.9 h1:wsNd0RuUxISqqudFqchsSsMqsM188DoZVPBeKl87tP0=
github.com/tetafro/godot v1.4.9/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8=
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro=
github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
@ -795,6 +802,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -881,8 +890,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -974,9 +983,10 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

26
pkg/monero/crypto.go Normal file
View file

@ -0,0 +1,26 @@
package monero
import (
"github.com/paxos-bankchain/moneroutil"
"golang.org/x/crypto/sha3"
)
func keccak256(data ...[]byte) []byte {
hash := sha3.NewLegacyKeccak256()
for _, v := range data {
hash.Write(v)
}
return hash.Sum(nil)
}
func publicKeyFromPrivateKey(private []byte) []byte {
public := make([]byte, KeySize)
p := new(moneroutil.ExtendedGroupElement)
moneroutil.GeScalarMultBase(p, (*moneroutil.Key)(private))
p.ToBytes((*moneroutil.Key)(public))
return public
}

29
pkg/monero/network.go Normal file
View file

@ -0,0 +1,29 @@
package monero
import "fmt"
// Network denotes a type of Monero network to gather information about.
//
type Network string
const (
NetworkMainnet Network = "mainnet"
NetworkTestnet Network = "testnet"
NetworkStagenet Network = "stagenet"
NetworkFakechain Network = "fakechain"
)
func (n Network) PublicAddressBase58Prefix() []byte {
switch n {
case NetworkMainnet:
return []byte{18}
case NetworkTestnet:
return []byte{53}
case NetworkStagenet:
return []byte{24}
case NetworkFakechain:
return NetworkMainnet.PublicAddressBase58Prefix()
}
panic(fmt.Errorf("'%s' is not a valid netowrk", n))
}

162
pkg/monero/seed.go Normal file
View file

@ -0,0 +1,162 @@
package monero
import (
"encoding/binary"
"hash/crc32"
"github.com/paxos-bankchain/moneroutil"
)
// KeySize denotes the size of the low-level public and private keys.
//
const KeySize = 32
// SeedOption describes the type of functional options that can be provided to
// the constructor to override default settings.
//
type SeedOption func(s *Seed)
// WithNetwork overrides the default network set for Seed instances.
//
func WithNetwork(n Network) SeedOption {
return func(s *Seed) {
s.network = n
}
}
// Seed encapsulates funcionality that arised from the knowledge of a private
// spend key.
//
// Differently from Bitcoin, Monero users posses two sets of private and public
// keys:
// private | public
// ------- | ------
// ks | Ks spend
// kv | Kv view
//
// From the private spend key, a private view key is derived. Of each of them,
// a corresponding public key is formed.
//
type Seed struct {
privateSpendKey []byte
network Network
privateViewKey []byte
publicSpendKey []byte
publicViewKey []byte
}
// NewSeed instantiates a new Seed instance based on a byte array that contains
// the secret number that derives all three other keys.
//
// As mentioned before, although here we store two private keys, ultimately,
// this `privateSpendKey` (an immense number "impossible" to guess that here is
// represented as a 32-byte array) is the only one key that is _really_
// important to safe-guard as the other one is derived out of it.
//
func NewSeed(privateSpendKey []byte, opts ...SeedOption) *Seed {
s := &Seed{
privateSpendKey: privateSpendKey,
network: NetworkMainnet,
privateViewKey: make([]byte, KeySize),
publicSpendKey: make([]byte, KeySize),
publicViewKey: make([]byte, KeySize),
}
for _, opt := range opts {
opt(s)
}
s.deriveKeys()
return s
}
// deriveKeys takes the private spend key and, out of it, derives all the other
// three keys:
//
// - private view
// - public spend
// - public view
//
func (s *Seed) deriveKeys() {
moneroutil.ScReduce32((*moneroutil.Key)(s.privateSpendKey))
s.privateViewKey = keccak256(s.privateSpendKey)
s.publicSpendKey = publicKeyFromPrivateKey(s.privateSpendKey)
s.publicViewKey = publicKeyFromPrivateKey(s.privateViewKey)
}
// Mnemonic converts a private spend key (that gigantic number represented as a
// 32-byte array) to a 25-element list of carefully chosen words in a
// particular language (a 1626 dictionary whose words have some nice
// properties).
//
// The first 24 words correspond to the key, with the 25-th being a CRC32
// checksum used for error-checking.
//
// ps.: in this implementation, only support for the English wordlist is provided.
//
func (s *Seed) Mnemonic() []string {
mnemonic := make([]string, 25)
wordlistSize := uint32(len(WordlistEnglish))
for i := 0; i < KeySize; i += 4 {
x := binary.LittleEndian.Uint32(s.privateSpendKey[i : i+4])
w1 := x % wordlistSize
w2 := (x/wordlistSize + w1) % wordlistSize
w3 := (x/wordlistSize/wordlistSize + w2) % wordlistSize
mnemonic[i/4*3] = WordlistEnglish[w1]
mnemonic[i/4*3+1] = WordlistEnglish[w2]
mnemonic[i/4*3+2] = WordlistEnglish[w3]
}
hash := crc32.NewIEEE()
for _, word := range mnemonic[:24] {
rrune := string([]rune(word)[:3])
hash.Write([]byte(rrune))
}
sum := hash.Sum32()
idx := sum % 24
mnemonic[24] = mnemonic[idx]
return mnemonic
}
// PrimaryAddress gives the base58-formatted representation of the primary
// address of this seed.
//
func (s *Seed) PrimaryAddress() string {
hash := keccak256(
s.network.PublicAddressBase58Prefix(),
s.publicSpendKey,
s.publicViewKey,
)
return moneroutil.EncodeMoneroBase58(
s.network.PublicAddressBase58Prefix(),
s.publicSpendKey,
s.publicViewKey,
hash[:4],
)
}
func (s *Seed) PrivateSpendKey() []byte {
return s.privateSpendKey
}
func (s *Seed) PrivateViewKey() []byte {
return s.privateViewKey
}
func (s *Seed) PublicSpendKey() []byte {
return s.publicSpendKey
}
func (s *Seed) PublicViewKey() []byte {
return s.publicViewKey
}

87
pkg/monero/seed_test.go Normal file
View file

@ -0,0 +1,87 @@
package monero_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/cirocosta/go-monero/pkg/monero"
)
func TestSeed(t *testing.T) {
for _, tc := range []struct {
name string
pk []byte
mnemonic []string
primaryAddress string
}{
{name: "full 0-s",
pk: []byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
mnemonic: []string{
"abbey", "abbey", "abbey", "abbey",
"abbey", "abbey", "abbey", "abbey",
"abbey", "abbey", "abbey", "abbey",
"abbey", "abbey", "abbey", "abbey",
"abbey", "abbey", "abbey", "abbey",
"abbey", "abbey", "abbey", "abbey",
"abbey",
},
primaryAddress: "41fJjQDhryD11111111111111111111111111111111112N1GuTZeagfRbbKcALdcZev4QXGGuoLh2x36LhaxLSxCc2YDhi",
},
{name: "first 8, and last 8",
pk: []byte{
1, 2, 3, 4, 5, 6, 7, 8,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
8, 7, 6, 5, 4, 3, 2, 1,
},
mnemonic: []string{
"object", "anxiety", "asked", "stockpile",
"saucepan", "skew", "abbey", "abbey",
"abbey", "abbey", "abbey", "abbey",
"abbey", "abbey", "abbey", "abbey",
"abbey", "abbey", "huddle", "excess",
"fever", "dagger", "nibs", "nineteen",
"abbey",
},
primaryAddress: "4953Se8CDGeZHr8sWmL61WNhKJatXZRSv6eJHB4hbBXF2RFmmKcxHQDR9i8nDkk94uHddmZohDAaPMcoqWgj4oMa748VFsf",
},
{name: "full 1-s",
pk: []byte{
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
},
mnemonic: []string{
"myriad", "vane", "vector", "myriad",
"vane", "vector", "myriad", "vane",
"vector", "myriad", "vane", "vector",
"myriad", "vane", "vector", "myriad",
"vane", "vector", "myriad", "vane",
"vector", "myriad", "vane", "vector",
"vector",
},
primaryAddress: "42Lxp5b63YJ8mVZTzcioVnCk9WQCPAMk4RH7e7ygPTkzEexugYvy5PvQS1MoWJE5ugbSCx9jYHgdbWhd8tQNvDbwFXdWDQC",
},
} {
t.Run(tc.name, func(t *testing.T) {
s := monero.NewSeed(tc.pk)
assert.Equal(t, tc.mnemonic, s.Mnemonic())
assert.Equal(t, tc.primaryAddress, s.PrimaryAddress())
})
}
}

1630
pkg/monero/wordlist.go Normal file
View file

@ -0,0 +1,1630 @@
package monero
var WordlistEnglish = []string{
"abbey",
"abducts",
"ability",
"ablaze",
"abnormal",
"abort",
"abrasive",
"absorb",
"abyss",
"academy",
"aces",
"aching",
"acidic",
"acoustic",
"acquire",
"across",
"actress",
"acumen",
"adapt",
"addicted",
"adept",
"adhesive",
"adjust",
"adopt",
"adrenalin",
"adult",
"adventure",
"aerial",
"afar",
"affair",
"afield",
"afloat",
"afoot",
"afraid",
"after",
"against",
"agenda",
"aggravate",
"agile",
"aglow",
"agnostic",
"agony",
"agreed",
"ahead",
"aided",
"ailments",
"aimless",
"airport",
"aisle",
"ajar",
"akin",
"alarms",
"album",
"alchemy",
"alerts",
"algebra",
"alkaline",
"alley",
"almost",
"aloof",
"alpine",
"already",
"also",
"altitude",
"alumni",
"always",
"amaze",
"ambush",
"amended",
"amidst",
"ammo",
"amnesty",
"among",
"amply",
"amused",
"anchor",
"android",
"anecdote",
"angled",
"ankle",
"annoyed",
"answers",
"antics",
"anvil",
"anxiety",
"anybody",
"apart",
"apex",
"aphid",
"aplomb",
"apology",
"apply",
"apricot",
"aptitude",
"aquarium",
"arbitrary",
"archer",
"ardent",
"arena",
"argue",
"arises",
"army",
"around",
"arrow",
"arsenic",
"artistic",
"ascend",
"ashtray",
"aside",
"asked",
"asleep",
"aspire",
"assorted",
"asylum",
"athlete",
"atlas",
"atom",
"atrium",
"attire",
"auburn",
"auctions",
"audio",
"august",
"aunt",
"austere",
"autumn",
"avatar",
"avidly",
"avoid",
"awakened",
"awesome",
"awful",
"awkward",
"awning",
"awoken",
"axes",
"axis",
"axle",
"aztec",
"azure",
"baby",
"bacon",
"badge",
"baffles",
"bagpipe",
"bailed",
"bakery",
"balding",
"bamboo",
"banjo",
"baptism",
"basin",
"batch",
"bawled",
"bays",
"because",
"beer",
"befit",
"begun",
"behind",
"being",
"below",
"bemused",
"benches",
"berries",
"bested",
"betting",
"bevel",
"beware",
"beyond",
"bias",
"bicycle",
"bids",
"bifocals",
"biggest",
"bikini",
"bimonthly",
"binocular",
"biology",
"biplane",
"birth",
"biscuit",
"bite",
"biweekly",
"blender",
"blip",
"bluntly",
"boat",
"bobsled",
"bodies",
"bogeys",
"boil",
"boldly",
"bomb",
"border",
"boss",
"both",
"bounced",
"bovine",
"bowling",
"boxes",
"boyfriend",
"broken",
"brunt",
"bubble",
"buckets",
"budget",
"buffet",
"bugs",
"building",
"bulb",
"bumper",
"bunch",
"business",
"butter",
"buying",
"buzzer",
"bygones",
"byline",
"bypass",
"cabin",
"cactus",
"cadets",
"cafe",
"cage",
"cajun",
"cake",
"calamity",
"camp",
"candy",
"casket",
"catch",
"cause",
"cavernous",
"cease",
"cedar",
"ceiling",
"cell",
"cement",
"cent",
"certain",
"chlorine",
"chrome",
"cider",
"cigar",
"cinema",
"circle",
"cistern",
"citadel",
"civilian",
"claim",
"click",
"clue",
"coal",
"cobra",
"cocoa",
"code",
"coexist",
"coffee",
"cogs",
"cohesive",
"coils",
"colony",
"comb",
"cool",
"copy",
"corrode",
"costume",
"cottage",
"cousin",
"cowl",
"criminal",
"cube",
"cucumber",
"cuddled",
"cuffs",
"cuisine",
"cunning",
"cupcake",
"custom",
"cycling",
"cylinder",
"cynical",
"dabbing",
"dads",
"daft",
"dagger",
"daily",
"damp",
"dangerous",
"dapper",
"darted",
"dash",
"dating",
"dauntless",
"dawn",
"daytime",
"dazed",
"debut",
"decay",
"dedicated",
"deepest",
"deftly",
"degrees",
"dehydrate",
"deity",
"dejected",
"delayed",
"demonstrate",
"dented",
"deodorant",
"depth",
"desk",
"devoid",
"dewdrop",
"dexterity",
"dialect",
"dice",
"diet",
"different",
"digit",
"dilute",
"dime",
"dinner",
"diode",
"diplomat",
"directed",
"distance",
"ditch",
"divers",
"dizzy",
"doctor",
"dodge",
"does",
"dogs",
"doing",
"dolphin",
"domestic",
"donuts",
"doorway",
"dormant",
"dosage",
"dotted",
"double",
"dove",
"down",
"dozen",
"dreams",
"drinks",
"drowning",
"drunk",
"drying",
"dual",
"dubbed",
"duckling",
"dude",
"duets",
"duke",
"dullness",
"dummy",
"dunes",
"duplex",
"duration",
"dusted",
"duties",
"dwarf",
"dwelt",
"dwindling",
"dying",
"dynamite",
"dyslexic",
"each",
"eagle",
"earth",
"easy",
"eating",
"eavesdrop",
"eccentric",
"echo",
"eclipse",
"economics",
"ecstatic",
"eden",
"edgy",
"edited",
"educated",
"eels",
"efficient",
"eggs",
"egotistic",
"eight",
"either",
"eject",
"elapse",
"elbow",
"eldest",
"eleven",
"elite",
"elope",
"else",
"eluded",
"emails",
"ember",
"emerge",
"emit",
"emotion",
"empty",
"emulate",
"energy",
"enforce",
"enhanced",
"enigma",
"enjoy",
"enlist",
"enmity",
"enough",
"enraged",
"ensign",
"entrance",
"envy",
"epoxy",
"equip",
"erase",
"erected",
"erosion",
"error",
"eskimos",
"espionage",
"essential",
"estate",
"etched",
"eternal",
"ethics",
"etiquette",
"evaluate",
"evenings",
"evicted",
"evolved",
"examine",
"excess",
"exhale",
"exit",
"exotic",
"exquisite",
"extra",
"exult",
"fabrics",
"factual",
"fading",
"fainted",
"faked",
"fall",
"family",
"fancy",
"farming",
"fatal",
"faulty",
"fawns",
"faxed",
"fazed",
"feast",
"february",
"federal",
"feel",
"feline",
"females",
"fences",
"ferry",
"festival",
"fetches",
"fever",
"fewest",
"fiat",
"fibula",
"fictional",
"fidget",
"fierce",
"fifteen",
"fight",
"films",
"firm",
"fishing",
"fitting",
"five",
"fixate",
"fizzle",
"fleet",
"flippant",
"flying",
"foamy",
"focus",
"foes",
"foggy",
"foiled",
"folding",
"fonts",
"foolish",
"fossil",
"fountain",
"fowls",
"foxes",
"foyer",
"framed",
"friendly",
"frown",
"fruit",
"frying",
"fudge",
"fuel",
"fugitive",
"fully",
"fuming",
"fungal",
"furnished",
"fuselage",
"future",
"fuzzy",
"gables",
"gadget",
"gags",
"gained",
"galaxy",
"gambit",
"gang",
"gasp",
"gather",
"gauze",
"gave",
"gawk",
"gaze",
"gearbox",
"gecko",
"geek",
"gels",
"gemstone",
"general",
"geometry",
"germs",
"gesture",
"getting",
"geyser",
"ghetto",
"ghost",
"giant",
"giddy",
"gifts",
"gigantic",
"gills",
"gimmick",
"ginger",
"girth",
"giving",
"glass",
"gleeful",
"glide",
"gnaw",
"gnome",
"goat",
"goblet",
"godfather",
"goes",
"goggles",
"going",
"goldfish",
"gone",
"goodbye",
"gopher",
"gorilla",
"gossip",
"gotten",
"gourmet",
"governing",
"gown",
"greater",
"grunt",
"guarded",
"guest",
"guide",
"gulp",
"gumball",
"guru",
"gusts",
"gutter",
"guys",
"gymnast",
"gypsy",
"gyrate",
"habitat",
"hacksaw",
"haggled",
"hairy",
"hamburger",
"happens",
"hashing",
"hatchet",
"haunted",
"having",
"hawk",
"haystack",
"hazard",
"hectare",
"hedgehog",
"heels",
"hefty",
"height",
"hemlock",
"hence",
"heron",
"hesitate",
"hexagon",
"hickory",
"hiding",
"highway",
"hijack",
"hiker",
"hills",
"himself",
"hinder",
"hippo",
"hire",
"history",
"hitched",
"hive",
"hoax",
"hobby",
"hockey",
"hoisting",
"hold",
"honked",
"hookup",
"hope",
"hornet",
"hospital",
"hotel",
"hounded",
"hover",
"howls",
"hubcaps",
"huddle",
"huge",
"hull",
"humid",
"hunter",
"hurried",
"husband",
"huts",
"hybrid",
"hydrogen",
"hyper",
"iceberg",
"icing",
"icon",
"identity",
"idiom",
"idled",
"idols",
"igloo",
"ignore",
"iguana",
"illness",
"imagine",
"imbalance",
"imitate",
"impel",
"inactive",
"inbound",
"incur",
"industrial",
"inexact",
"inflamed",
"ingested",
"initiate",
"injury",
"inkling",
"inline",
"inmate",
"innocent",
"inorganic",
"input",
"inquest",
"inroads",
"insult",
"intended",
"inundate",
"invoke",
"inwardly",
"ionic",
"irate",
"iris",
"irony",
"irritate",
"island",
"isolated",
"issued",
"italics",
"itches",
"items",
"itinerary",
"itself",
"ivory",
"jabbed",
"jackets",
"jaded",
"jagged",
"jailed",
"jamming",
"january",
"jargon",
"jaunt",
"javelin",
"jaws",
"jazz",
"jeans",
"jeers",
"jellyfish",
"jeopardy",
"jerseys",
"jester",
"jetting",
"jewels",
"jigsaw",
"jingle",
"jittery",
"jive",
"jobs",
"jockey",
"jogger",
"joining",
"joking",
"jolted",
"jostle",
"journal",
"joyous",
"jubilee",
"judge",
"juggled",
"juicy",
"jukebox",
"july",
"jump",
"junk",
"jury",
"justice",
"juvenile",
"kangaroo",
"karate",
"keep",
"kennel",
"kept",
"kernels",
"kettle",
"keyboard",
"kickoff",
"kidneys",
"king",
"kiosk",
"kisses",
"kitchens",
"kiwi",
"knapsack",
"knee",
"knife",
"knowledge",
"knuckle",
"koala",
"laboratory",
"ladder",
"lagoon",
"lair",
"lakes",
"lamb",
"language",
"laptop",
"large",
"last",
"later",
"launching",
"lava",
"lawsuit",
"layout",
"lazy",
"lectures",
"ledge",
"leech",
"left",
"legion",
"leisure",
"lemon",
"lending",
"leopard",
"lesson",
"lettuce",
"lexicon",
"liar",
"library",
"licks",
"lids",
"lied",
"lifestyle",
"light",
"likewise",
"lilac",
"limits",
"linen",
"lion",
"lipstick",
"liquid",
"listen",
"lively",
"loaded",
"lobster",
"locker",
"lodge",
"lofty",
"logic",
"loincloth",
"long",
"looking",
"lopped",
"lordship",
"losing",
"lottery",
"loudly",
"love",
"lower",
"loyal",
"lucky",
"luggage",
"lukewarm",
"lullaby",
"lumber",
"lunar",
"lurk",
"lush",
"luxury",
"lymph",
"lynx",
"lyrics",
"macro",
"madness",
"magically",
"mailed",
"major",
"makeup",
"malady",
"mammal",
"maps",
"masterful",
"match",
"maul",
"maverick",
"maximum",
"mayor",
"maze",
"meant",
"mechanic",
"medicate",
"meeting",
"megabyte",
"melting",
"memoir",
"menu",
"merger",
"mesh",
"metro",
"mews",
"mice",
"midst",
"mighty",
"mime",
"mirror",
"misery",
"mittens",
"mixture",
"moat",
"mobile",
"mocked",
"mohawk",
"moisture",
"molten",
"moment",
"money",
"moon",
"mops",
"morsel",
"mostly",
"motherly",
"mouth",
"movement",
"mowing",
"much",
"muddy",
"muffin",
"mugged",
"mullet",
"mumble",
"mundane",
"muppet",
"mural",
"musical",
"muzzle",
"myriad",
"mystery",
"myth",
"nabbing",
"nagged",
"nail",
"names",
"nanny",
"napkin",
"narrate",
"nasty",
"natural",
"nautical",
"navy",
"nearby",
"necklace",
"needed",
"negative",
"neither",
"neon",
"nephew",
"nerves",
"nestle",
"network",
"neutral",
"never",
"newt",
"nexus",
"nibs",
"niche",
"niece",
"nifty",
"nightly",
"nimbly",
"nineteen",
"nirvana",
"nitrogen",
"nobody",
"nocturnal",
"nodes",
"noises",
"nomad",
"noodles",
"northern",
"nostril",
"noted",
"nouns",
"novelty",
"nowhere",
"nozzle",
"nuance",
"nucleus",
"nudged",
"nugget",
"nuisance",
"null",
"number",
"nuns",
"nurse",
"nutshell",
"nylon",
"oaks",
"oars",
"oasis",
"oatmeal",
"obedient",
"object",
"obliged",
"obnoxious",
"observant",
"obtains",
"obvious",
"occur",
"ocean",
"october",
"odds",
"odometer",
"offend",
"often",
"oilfield",
"ointment",
"okay",
"older",
"olive",
"olympics",
"omega",
"omission",
"omnibus",
"onboard",
"oncoming",
"oneself",
"ongoing",
"onion",
"online",
"onslaught",
"onto",
"onward",
"oozed",
"opacity",
"opened",
"opposite",
"optical",
"opus",
"orange",
"orbit",
"orchid",
"orders",
"organs",
"origin",
"ornament",
"orphans",
"oscar",
"ostrich",
"otherwise",
"otter",
"ouch",
"ought",
"ounce",
"ourselves",
"oust",
"outbreak",
"oval",
"oven",
"owed",
"owls",
"owner",
"oxidant",
"oxygen",
"oyster",
"ozone",
"pact",
"paddles",
"pager",
"pairing",
"palace",
"pamphlet",
"pancakes",
"paper",
"paradise",
"pastry",
"patio",
"pause",
"pavements",
"pawnshop",
"payment",
"peaches",
"pebbles",
"peculiar",
"pedantic",
"peeled",
"pegs",
"pelican",
"pencil",
"people",
"pepper",
"perfect",
"pests",
"petals",
"phase",
"pheasants",
"phone",
"phrases",
"physics",
"piano",
"picked",
"pierce",
"pigment",
"piloted",
"pimple",
"pinched",
"pioneer",
"pipeline",
"pirate",
"pistons",
"pitched",
"pivot",
"pixels",
"pizza",
"playful",
"pledge",
"pliers",
"plotting",
"plus",
"plywood",
"poaching",
"pockets",
"podcast",
"poetry",
"point",
"poker",
"polar",
"ponies",
"pool",
"popular",
"portents",
"possible",
"potato",
"pouch",
"poverty",
"powder",
"pram",
"present",
"pride",
"problems",
"pruned",
"prying",
"psychic",
"public",
"puck",
"puddle",
"puffin",
"pulp",
"pumpkins",
"punch",
"puppy",
"purged",
"push",
"putty",
"puzzled",
"pylons",
"pyramid",
"python",
"queen",
"quick",
"quote",
"rabbits",
"racetrack",
"radar",
"rafts",
"rage",
"railway",
"raking",
"rally",
"ramped",
"randomly",
"rapid",
"rarest",
"rash",
"rated",
"ravine",
"rays",
"razor",
"react",
"rebel",
"recipe",
"reduce",
"reef",
"refer",
"regular",
"reheat",
"reinvest",
"rejoices",
"rekindle",
"relic",
"remedy",
"renting",
"reorder",
"repent",
"request",
"reruns",
"rest",
"return",
"reunion",
"revamp",
"rewind",
"rhino",
"rhythm",
"ribbon",
"richly",
"ridges",
"rift",
"rigid",
"rims",
"ringing",
"riots",
"ripped",
"rising",
"ritual",
"river",
"roared",
"robot",
"rockets",
"rodent",
"rogue",
"roles",
"romance",
"roomy",
"roped",
"roster",
"rotate",
"rounded",
"rover",
"rowboat",
"royal",
"ruby",
"rudely",
"ruffled",
"rugged",
"ruined",
"ruling",
"rumble",
"runway",
"rural",
"rustled",
"ruthless",
"sabotage",
"sack",
"sadness",
"safety",
"saga",
"sailor",
"sake",
"salads",
"sample",
"sanity",
"sapling",
"sarcasm",
"sash",
"satin",
"saucepan",
"saved",
"sawmill",
"saxophone",
"sayings",
"scamper",
"scenic",
"school",
"science",
"scoop",
"scrub",
"scuba",
"seasons",
"second",
"sedan",
"seeded",
"segments",
"seismic",
"selfish",
"semifinal",
"sensible",
"september",
"sequence",
"serving",
"session",
"setup",
"seventh",
"sewage",
"shackles",
"shelter",
"shipped",
"shocking",
"shrugged",
"shuffled",
"shyness",
"siblings",
"sickness",
"sidekick",
"sieve",
"sifting",
"sighting",
"silk",
"simplest",
"sincerely",
"sipped",
"siren",
"situated",
"sixteen",
"sizes",
"skater",
"skew",
"skirting",
"skulls",
"skydive",
"slackens",
"sleepless",
"slid",
"slower",
"slug",
"smash",
"smelting",
"smidgen",
"smog",
"smuggled",
"snake",
"sneeze",
"sniff",
"snout",
"snug",
"soapy",
"sober",
"soccer",
"soda",
"software",
"soggy",
"soil",
"solved",
"somewhere",
"sonic",
"soothe",
"soprano",
"sorry",
"southern",
"sovereign",
"sowed",
"soya",
"space",
"speedy",
"sphere",
"spiders",
"splendid",
"spout",
"sprig",
"spud",
"spying",
"square",
"stacking",
"stellar",
"stick",
"stockpile",
"strained",
"stunning",
"stylishly",
"subtly",
"succeed",
"suddenly",
"suede",
"suffice",
"sugar",
"suitcase",
"sulking",
"summon",
"sunken",
"superior",
"surfer",
"sushi",
"suture",
"swagger",
"swept",
"swiftly",
"sword",
"swung",
"syllabus",
"symptoms",
"syndrome",
"syringe",
"system",
"taboo",
"tacit",
"tadpoles",
"tagged",
"tail",
"taken",
"talent",
"tamper",
"tanks",
"tapestry",
"tarnished",
"tasked",
"tattoo",
"taunts",
"tavern",
"tawny",
"taxi",
"teardrop",
"technical",
"tedious",
"teeming",
"tell",
"template",
"tender",
"tepid",
"tequila",
"terminal",
"testing",
"tether",
"textbook",
"thaw",
"theatrics",
"thirsty",
"thorn",
"threaten",
"thumbs",
"thwart",
"ticket",
"tidy",
"tiers",
"tiger",
"tilt",
"timber",
"tinted",
"tipsy",
"tirade",
"tissue",
"titans",
"toaster",
"tobacco",
"today",
"toenail",
"toffee",
"together",
"toilet",
"token",
"tolerant",
"tomorrow",
"tonic",
"toolbox",
"topic",
"torch",
"tossed",
"total",
"touchy",
"towel",
"toxic",
"toyed",
"trash",
"trendy",
"tribal",
"trolling",
"truth",
"trying",
"tsunami",
"tubes",
"tucks",
"tudor",
"tuesday",
"tufts",
"tugs",
"tuition",
"tulips",
"tumbling",
"tunnel",
"turnip",
"tusks",
"tutor",
"tuxedo",
"twang",
"tweezers",
"twice",
"twofold",
"tycoon",
"typist",
"tyrant",
"ugly",
"ulcers",
"ultimate",
"umbrella",
"umpire",
"unafraid",
"unbending",
"uncle",
"under",
"uneven",
"unfit",
"ungainly",
"unhappy",
"union",
"unjustly",
"unknown",
"unlikely",
"unmask",
"unnoticed",
"unopened",
"unplugs",
"unquoted",
"unrest",
"unsafe",
"until",
"unusual",
"unveil",
"unwind",
"unzip",
"upbeat",
"upcoming",
"update",
"upgrade",
"uphill",
"upkeep",
"upload",
"upon",
"upper",
"upright",
"upstairs",
"uptight",
"upwards",
"urban",
"urchins",
"urgent",
"usage",
"useful",
"usher",
"using",
"usual",
"utensils",
"utility",
"utmost",
"utopia",
"uttered",
"vacation",
"vague",
"vain",
"value",
"vampire",
"vane",
"vapidly",
"vary",
"vastness",
"vats",
"vaults",
"vector",
"veered",
"vegan",
"vehicle",
"vein",
"velvet",
"venomous",
"verification",
"vessel",
"veteran",
"vexed",
"vials",
"vibrate",
"victim",
"video",
"viewpoint",
"vigilant",
"viking",
"village",
"vinegar",
"violin",
"vipers",
"virtual",
"visited",
"vitals",
"vivid",
"vixen",
"vocal",
"vogue",
"voice",
"volcano",
"vortex",
"voted",
"voucher",
"vowels",
"voyage",
"vulture",
"wade",
"waffle",
"wagtail",
"waist",
"waking",
"wallets",
"wanted",
"warped",
"washing",
"water",
"waveform",
"waxing",
"wayside",
"weavers",
"website",
"wedge",
"weekday",
"weird",
"welders",
"went",
"wept",
"were",
"western",
"wetsuit",
"whale",
"when",
"whipped",
"whole",
"wickets",
"width",
"wield",
"wife",
"wiggle",
"wildly",
"winter",
"wipeout",
"wiring",
"wise",
"withdrawn",
"wives",
"wizard",
"wobbly",
"woes",
"woken",
"wolf",
"womanly",
"wonders",
"woozy",
"worry",
"wounded",
"woven",
"wrap",
"wrist",
"wrong",
"yacht",
"yahoo",
"yanks",
"yard",
"yawning",
"yearbook",
"yellow",
"yesterday",
"yeti",
"yields",
"yodel",
"yoga",
"younger",
"yoyo",
"zapped",
"zeal",
"zebra",
"zero",
"zesty",
"zigzags",
"zinger",
"zippers",
"zodiac",
"zombie",
"zones",
"zoom",
}