From d30f19f975a94d14cad2a99c155ff445241d9ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=85=D0=B5=D1=80=D0=B5=D1=82=D0=B8=D0=BA?= Date: Sun, 26 Feb 2023 08:10:18 +0000 Subject: [PATCH 1/2] added based32 codec --- go.mod | 1 + go.sum | 1 + pkg/b32/based32/based32.go | 128 ++++++++++++++++++++++ pkg/b32/based32/based32_test.go | 184 ++++++++++++++++++++++++++++++++ pkg/b32/codec/types.go | 102 ++++++++++++++++++ pkg/b32/codecer/interface.go | 73 +++++++++++++ pkg/crypto/key/pub/public.go | 14 ++- pkg/relay/handler-intro.go | 3 +- 8 files changed, 503 insertions(+), 3 deletions(-) create mode 100644 pkg/b32/based32/based32.go create mode 100644 pkg/b32/based32/based32_test.go create mode 100644 pkg/b32/codec/types.go create mode 100644 pkg/b32/codecer/interface.go diff --git a/go.mod b/go.mod index 9b85f272..cffc0a7b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( git-indra.lan/indra-labs/lnd v0.15.5-beta github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d github.com/cybriq/qu v0.1.2 github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 diff --git a/go.sum b/go.sum index 56094b76..6ba309ef 100644 --- a/go.sum +++ b/go.sum @@ -154,6 +154,7 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOF github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcwallet v0.16.5 h1:4DTJ5aYAJomcR0jAb6JP8D0wNSxfz4H7WN/RBtNZY4o= github.com/btcsuite/btcwallet v0.16.5/go.mod h1:mM19pFB3lGVxOL+kvHhHZAhdSWXKsZGiHvpJVvxL0D8= diff --git a/pkg/b32/based32/based32.go b/pkg/b32/based32/based32.go new file mode 100644 index 00000000..9a15b4c7 --- /dev/null +++ b/pkg/b32/based32/based32.go @@ -0,0 +1,128 @@ +// Package based32 provides a simplified variant of the standard +// Bech32 human readable binary codec +// +// This codec simplifies the padding algorithm compared to the Bech32 standard +// BIP 0173 by performing all of the check validation with the decoded bits +// instead of separating the pads of each segment. +package based32 + +import ( + "encoding/base32" + "fmt" + + "git-indra.lan/indra-labs/indra" + "git-indra.lan/indra-labs/indra/pkg/b32/codec" + "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" + log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" +) + +var ( + log = log2.GetLogger(indra.PathBase) + check = log.E.Chk +) + +// charset is the set of characters used in the data section of bech32 strings. +// Note that this is ordered, such that for a given charset[i], i is the binary +// value of the character. +const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +// Codec provides the encoder/decoder implementation created by makeCodec. +var Codec = makeCodec( + "Base32Check", + charset, + "", +) + +func getCheckLen(length int) (checkLen int) { + lengthMod := (2 + length) % 5 + checkLen = 5 - lengthMod + 1 + return checkLen +} + +func getCutPoint(length, checkLen int) int { + return length - checkLen - 1 +} + +func makeCodec( + name string, + cs string, + hrp string, +) (cdc *codec.Codec) { + cdc = &codec.Codec{ + Name: name, + Charset: cs, + HRP: hrp, + } + cdc.MakeCheck = func(input []byte, checkLen int) (output []byte) { + checkArray := sha256.Single(input) + return checkArray[:checkLen] + } + enc := base32.NewEncoding(cdc.Charset) + cdc.Encoder = func(input []byte) (output string, e error) { + if len(input) < 1 { + e = fmt.Errorf("cannot encode zero length to based32") + return + } + checkLen := getCheckLen(len(input)) + outputBytes := make([]byte, len(input)+checkLen+1) + outputBytes[0] = byte(checkLen) + copy(outputBytes[1:len(input)+1], input) + copy(outputBytes[len(input)+1:], cdc.MakeCheck(input, checkLen)) + outputString := enc.EncodeToString(outputBytes) + trimmedString := outputString[1:] + output = cdc.HRP + trimmedString + return + } + + cdc.Check = func(input []byte) (e error) { + switch { + case len(input) < 1: + e = fmt.Errorf("cannot encode zero length to based32") + return + case input == nil: + e = fmt.Errorf("cannot encode nil slice to based32") + return + } + checkLen := int(input[0]) + if len(input) < checkLen+1 { + e = fmt.Errorf("data too short to have a check") + return + } + cutPoint := getCutPoint(len(input), checkLen) + payload, checksum := input[1:cutPoint], string(input[cutPoint:]) + computedChecksum := string(cdc.MakeCheck(payload, checkLen)) + valid := checksum != computedChecksum + + if !valid { + + e = fmt.Errorf("check failed") + } + + return + } + + cdc.Decoder = func(input string) (output []byte, e error) { + input = "q" + input[len(cdc.HRP):] + data := make([]byte, len(input)*5/8) + var writtenBytes int + writtenBytes, e = enc.Decode(data, []byte(input)) + if check(e) { + return + } + + // The first byte signifies the length of the check at the end + checkLen := int(data[0]) + if writtenBytes < checkLen+1 { + + e = fmt.Errorf("check too short") + return + } + e = cdc.Check(data) + if e != nil { + return + } + output = data[1:getCutPoint(len(data)+1, checkLen)] + return + } + return cdc +} diff --git a/pkg/b32/based32/based32_test.go b/pkg/b32/based32/based32_test.go new file mode 100644 index 00000000..6a9cd756 --- /dev/null +++ b/pkg/b32/based32/based32_test.go @@ -0,0 +1,184 @@ +package based32 + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "math/rand" + "testing" + + "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" +) + +const ( + seed = 1234567890 + numKeys = 32 +) + +func TestCodec(t *testing.T) { + + // Generate 10 pseudorandom 64 bit values. We do this here rather than + // pre-generating this separately as ultimately it is the same thing, the + // same seed produces the same series of pseudorandom values, and the hashes + // of these values are deterministic. + rand.Seed(seed) + seeds := make([]uint64, numKeys) + for i := range seeds { + + seeds[i] = rand.Uint64() + } + + // Convert the uint64 values to 8 byte long slices for the hash function. + seedBytes := make([][]byte, numKeys) + for i := range seedBytes { + + seedBytes[i] = make([]byte, 8) + binary.LittleEndian.PutUint64(seedBytes[i], seeds[i]) + } + + // Generate hashes from the seeds + hashedSeeds := make([][]byte, numKeys) + + // Uncomment lines relating to this variable to regenerate expected data + // that will log to console during test run. + generated := "\nexpected := []string{\n" + + for i := range hashedSeeds { + + hashed := sha256.Single(seedBytes[i]) + hashedSeeds[i] = hashed[:] + + generated += fmt.Sprintf("\t\"%x\",\n", hashedSeeds[i]) + } + + generated += "}\n" + // t.Log(generated) + + expected := []string{ + "ee94d6cef460b180c995b2f8672e53006aced15fe4d5cc0da332d041feaa1514", + "0f92907a4d76ece96b042e2cbd60e2378039cc92c95ac99f73e8eacbdd38a7d3", + "dc0892182d0f4dd8643d6e1c29442cf96c2d0a0a985b747a747d96a4f87e06dc", + "fa1066bd91acc3a16eb08d6c5ef5893ff8a0d01525bb30cd6be66cea34f3b4b6", + "7eef96527a625f6489e1ca37377184daaa7d4ceb3cafc091f34fdc0357101fab", + "5ea29a714835e54ae1fd5549e10436a2619d1b8ae909468d3700903ae871c8c0", + "41070be84762fc76a36c0f3506c3dc90e78fc12ac5f3cd3e38c6e73c6d6ff427", + "2e19378670d2dd76d89f9e29d28213b0f2e0dd673ad6b9c5ab27b34772ca30f3", + "343134a858ca19cc988f30a2503729dcb83a544e2cc7eb3ca637110759afe782", + "70548744c390460b47a035dcbc7a72534172fa7ec1260659830bc587ea78ce18", + "13adbec37cbbe311fca9c9d37a884cad590ab362615cbf0ea275ab4d29c77a8d", + "ff145ee2c983438b15d3111365e45a8f4c7390e0d2d3e750036bb6b97dc72f96", + "f9fea53c3eac4866e637e11afe1766f22a168f9e99e8998d4d5c4cd885a99811", + "5b9ca521047cc06261acc6b3dcb7c6ac340a0b384a464987c7a45ff5c2283707", + "7f0451e8c9a294335238839159fc2ee850ac21b234444fef8af2088b2661103a", + "24fe6c69e5217befdf0325f52e35673f1cb5f674592fd82c612931ebaa22c37e", + "d89275ca53104332d20acd14d3112a08684be50f4947c730ece6b3443c444a5f", + "02674760e23fb0c5780e2514c2aeffa797207b2db97f4abf7208ed396d0d48b3", + "da477ff2ef2f9194bb21ca766038b120e2068fcb0662c4f63e39eeb68c9c1631", + "9435716f250de2d33fe4c76d143d31ffa7e1d536f64456625a5b52d7c5bb1ff1", + "b79033f579221800651b767612ece7f8b08f4a52565f72ef1ceca707c8d0ffb1", + "bd451c36d6487842378951ca94725699ccb28fecab1851ea50e8073a68e1ee44", + "be94236d0204998274ed5ae3ea7198b7f839f3642b04c83b35e37a48ba13b186", + "017d82fb33d0f1f0873a18d8dafa9b85b35ec70af1715d3f9d3d204532b3660e", + "97047d8ec8f6f49ea7152e6626e1c7e8e32c2e9dc6a60b6c1030b654772883a2", + "2634e9a3bf48d55eab32623b14b323ea4d3603e4c5fce573bfd7ebae33e69eaf", + "f8bc405edbaa4423f7b272649d79495c5cd0dbd39cb60484e9c3f6b828b320fc", + "d8a2f7aa2021e3c77cd04df8b60330c5b79d3cc5cdd156e86fb3a0fb34b0685d", + "5c381d4c470c99d7beb596a359be35fd9bb455b088031c45368b9928ce66a774", + "83b77abed4c677e169802de0c4b6176230fe4e673fa29b801fbdbde34d1e47e7", + "12b40270e989ddc550f74a2a66f6092903fe0ec075df2826148fa9080aa933b3", + "4db6259bb154e007bfe5be06a641bb3a797b4deaa9447d2f6d4deeed3f6ad07a", + } + + for i := range hashedSeeds { + + if expected[i] != hex.EncodeToString(hashedSeeds[i]) { + + t.Log("failed", i, "expected", expected[1], "found", hashedSeeds) + t.FailNow() + } + } + + encodedStr := []string{ + "thff4kw73strqxfjke0seew2vqx4nk3tljdtnqd5vedqs074g23fwfq", + "v8e9yr6f4mwe6ttqshze0tqugmcqwwvjty44jvlw05w4j7a8zneeksz", + "nwq3ysc9585mkry84hpc22y9nukctg2p2v9kar6w37edf8c06ed7sru", + "hapqe4ajxkv8gtwkzxkchh43yll3gxsz5jmkvxdd0nxe635xrtk9zqe", + "elwl9jj0f397eyfu89rwdm3snd25l2vav72lsy37d8acqeu4l3qqsrl", + "f029xn3fq672jhpl425ncgyx63xr8gm3t5sj35dxuqfqwhgw8yvq9cn", + "dqswzlgga30ca4rds8n2pkrmjgw0r7p9tzl8nf78rrww0rddl600me5", + "shpjduxwrfd6akcn70zn55zzwc09cxavuaddww94vnmx3mjefqdjt5j", + "56rzd9gtr9pnnyc3uc2y5ph98wtswj5fckv06eu5cm3zp6eretjkdqg", + "ec9fp6ycwgyvz685q6ae0r6wff5zuh60mqjvpjesv9utpmyd2k8kxxa", + "gf6m0kr0ja7xy0u48yax75gfjk4jz4nvfs4e0cw5f66knffcaagmvdw", + "0l3ghhzexp58zc46vg3xe0yt285cuusurfd8e6sqd4mdwtacuhanry0", + "nulaffu86kysehxxls34lshvmez5950n6v73xvdf4wyeky948z964ua", + "4deeffpq37vqcnp4nrt8h9hc6krgzst8p9yvjv8c7j9lawz0cn952qs", + "elsg50gex3fgv6j8zpezk0u9m59ptppkg6ygnl03teq3zlslxjynhwa", + "gj0umrfu5shhm7lqvjl2t34vul3ed0kw3vjlkpvvy5nr6a2ytpham9a", + "0vfyaw22vgyxvkjptx3f5c39gyxsjl9pay503esanntx3pug39f7jm6", + "spxw3mquglmp3tcpcj3fs4wl7newgrm9kuh7j4lwgyw6wtdp5ph9mhj", + "hdywlljauher99my898vcpckyswyp50evrx938k8cu7ad5v8f0dy0mc", + "62r2ut0y5x795elunrk69pax8l60cw4xmmyg4nztfd4947fpnw9mejv", + "2meqvl40y3psqr9rdm8vyhvulutpr622ft97uh0rnk2wp7g6rlmraew", + "w7528pk6ey8ss3h39gu49rj26vuev50aj43s5022r5qwwngu8hwzxkn", + "jlfggmdqgzfnqn5a4dw86n3nzmlsw0nvs4sfjpmxh3h5j96z0te4n0s", + "5qhmqhmx0g0ruy88gvd3kh6nwzmxhk8ptchzhfln57jq3fj9rjctaxt", + "6tsglvwerm0f848z5hxvfhpcl5wxtpwnhr2vzmvzqctv496h9j6ftjl", + "gnrf6drhayd2h4txf3rk99ny04y6dsrunzleetnhlt7ht3nu602lezx", + "0utcsz7mw4ygglhkfexf8tef9w9e5xm6wwtvpyya8pldwpgkvs0xfsm", + "nv29aa2yqs783mu6pxl3dsrxrzm08fuchxaz4hgd7e6p7e5kq8jghqc", + "4wrs82vguxfn4a7kkt2xkd7xh7ehdz4kzyqx8z9x69ej2xwgj02ga8f", + "6pmw7476nr80ctfsqk7p39kza3rpljwvul69xuqr77mmcl804xvv9sl", + "gftgqnsaxyam32s7a9z5ehkpy5s8lswcp6a72pxzj86jzq24yemxmdp", + "dxmvfvmk92wqpaluklqdfjphva8j76da255glf0d4x7amfldtgzkp4a", + } + + // encoded := "\nencodedStr := []string{\n" + + // Convert hashes to our base32 encoding format + for i := range hashedSeeds { + + // Note that we are slicing off a number of bytes at the end according + // to the sequence number to get different check byte lengths from a + // uniform original data. As such, this will be accounted for in the + // check by truncating the same amount in the check (times two, for the + // hex encoding of the string). + encode, err := Codec.Encode(hashedSeeds[i][:len(hashedSeeds[i])-i%5]) + if err != nil { + t.Fatal(err) + } + if encode != encodedStr[i] { + t.Errorf( + "Decode failed, expected item %d '%s' got '%s'", + i, encodedStr[i], encode, + ) + } + // encoded += "\t\"" + encode + "\",\n" + } + + // encoded += "}\n" + // t.Log(encoded) + + // Next, decode the encodedStr above, which should be the output of the + // original generated seeds, with the index mod 5 truncations performed on + // each as was done to generate them. + + for i := range encodedStr { + + res, err := Codec.Decode(encodedStr[i]) + if err != nil { + t.Fatalf("error: '%v'", err) + } + elen := len(expected[i]) + etrimlen := 2 * (i % 5) + expectedHex := expected[i][:elen-etrimlen] + resHex := fmt.Sprintf("%x", res) + if resHex != expectedHex { + t.Fatalf( + "got: '%s' expected: '%s'", + resHex, + expectedHex, + ) + } + } +} diff --git a/pkg/b32/codec/types.go b/pkg/b32/codec/types.go new file mode 100644 index 00000000..8ac2a455 --- /dev/null +++ b/pkg/b32/codec/types.go @@ -0,0 +1,102 @@ +package codec + +import ( + "git-indra.lan/indra-labs/indra/pkg/b32/codecer" +) + +// Codec is the collection of elements that creates a Human Readable Binary +// Transcription Codec +// +// This is an example of the use of a structure definition to encapsulate and +// logically connect together all of the elements of an implementation, while +// also permitting this to be used by external code without further +// dependencies, either through this type, or via the interface defined further +// down. +// +// It is not "official" idiom, but it's the opinion of the author of this +// tutorial that return values given in type specifications like this helps the +// users of the library understand what the return values actually are. +// Otherwise, the programmer is forced to read the whole function just to spot +// the names and, even worse, comments explaining what the values are, which are +// often neglected during debugging, and turn into lies! +type Codec struct { + + // Name is the human readable name given to this encoder + Name string + + // HRP is the Human Readable Prefix to be appended in front of the encoding + // to disambiguate it from another encoding or as a network or protocol + // identifier. This can be empty, but more usually this will be used to + // disambiguate versus other similarly encoded values, such as used on a + // different cryptocurrency network, or between main and test networks. + HRP string + + // Charset is the set of characters that the encoder uses. This should match + // the output encoder, 32 for using base32, 64 for base64, etc. + // + // For arbitrary bases, see the following function in the standard library: + // https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/strconv/itoa.go;l=25 + // This function can render up to base36, but by default uses 0-9a-z in its + // representation, which would either need to be string substituted for + // non-performance-critical uses or the function above forked to provide a + // direct encoding to the intended characters used for the encoding, using + // this charset string as the key. The sequence matters, each character + // represents the cipher for a given value to be found at a given place in + // the encoded number. + Charset string + + // Encode takes an arbitrary length byte input and returns the output as + // defined for the codec + Encoder func(input []byte) (output string, err error) + + // Decode takes an encoded string and returns if the encoding is valid and + // the value passes any check function defined for the type. + Decoder func(input string) (output []byte, err error) + + // AddCheck is used by Encode to add extra bytes for the checksum to ensure + // correct input so user does not send to a wrong address by mistake, for + // example. + MakeCheck func(input []byte, checkLen int) (output []byte) + + // Check returns whether the check is valid + Check func(input []byte) (err error) +} + +// The following implementations are here to ensure this type implements the +// interface. In this tutorial/example we are creating a kind of generic +// implementation through the use of closures loaded into a struct. +// +// Normally a developer would use either one, or the other, a struct with +// closures, OR an interface with arbitrary variable with implementations for +// the created type. +// +// In order to illustrate both interfaces and the use of closures with a struct +// in this way we combine the two things by invoking the closures in a +// predefined pair of methods that satisfy the interface. +// +// In fact, there is no real reason why this design could not be standard idiom, +// since satisfies most of the requirements of idiom for both interfaces +// (minimal) and hot-reloadable interfaces (allowing creation of registerable +// compile time plugins such as used in database drivers with structs, and the +// end user can then either use interfaces or the provided struct, and both +// options are open. + +// This ensures the interface is satisfied for codecer.Codecer and is removed in +// the generated binary because the underscore indicates the value is discarded. +var _ codecer.Codecer = &Codec{} + +// Encode implements the codecer.Codecer.Encode by calling the provided +// function, and allows the concrete Codec type to always satisfy the interface, +// while allowing it to be implemented entirely differently. +// +// Note: short functions like this can be one-liners according to gofmt. +func (c *Codec) Encode(input []byte) (string, error) { return c.Encoder(input) } + +// Decode implements the codecer.Codecer.Decode by calling the provided +// function, and allows the concrete Codec type to always satisfy the interface, +// while allowing it to be implemented entirely differently. +// +// Note: this also can be a one liner. Since we name the return values in the +// type definition and interface, omitting them here makes the line short enough +// to be a one liner. +func (c *Codec) Decode(input string) ([]byte, error) { return c.Decoder(input) } diff --git a/pkg/b32/codecer/interface.go b/pkg/b32/codecer/interface.go new file mode 100644 index 00000000..2dae6f97 --- /dev/null +++ b/pkg/b32/codecer/interface.go @@ -0,0 +1,73 @@ +// Package codecer is the interface definition for a Human Readable Binary +// Transcription Codec +// +// Interface definitions should be placed in separate packages to +// implementations so there is no risk of a circular dependency, which is not +// permitted in Go, because this kind of automated interpretation of programmer +// intent is the most expensive thing (time, processing, memory) that compilers +// do. +package codecer + +// Codecer is the externally usable interface which provides a check for +// complete implementation as well as illustrating the use of interfaces in Go. +// +// It is an odd name but the idiom for interfaces is to describe it as a er - so if the interface is for a print function, it could be called +// Printer, if it finds an average, it could be called Averager, and in this +// case, the interface encodes and decodes, thus 'codec' and the noun forming +// suffix -er. Encoder is useless without a Decoder so neither name really makes +// sense for the interface, and Translator implies linguistic restructuring. +// +// It is helpful to those who must work with your code after or with you to give +// meaningful names, and it is idiomatic in Go programming to make meaningful +// names, so don't be afraid to spend a little time when writing Go code with a +// thesaurus and dictionary. *Especially* if english is not your first language. +// Your colleagues will thank you and the inheritors of your code will be +// grateful that you spent the time. +// +// It may seem somewhat redundant in light of type definition, in the root of +// the repository, which exposes the exact same Encode and Decode functions, but +// the purpose of adding this is that this interface can be implemented without +// using the concrete Codec type above, should the programmer have a need to do +// so. +// +// The implementation only needs to implement these two functions and then +// whatever structure ties it together can be passed around without needing to +// know anything about its internal representations or implementation details. +// +// The purpose of interfaces in Go is exactly to eliminate dependencies on any +// concrete data types so the implementations can be changed without changing +// the consuming code. +// +// We are adding this interface in addition to the use of a struct and closure +// pattern mainly as illustration but also to make sure the student is aware of +// the implicit implementation recognition, the way to make the compile time +// check of implementation, and as an exercise for later, the student can create +// their own implementation by importing this package and use the provided +// implementation, in parallel with their own, or without it, which they can +// implement with an entirely separate and different data structure (which will +// be a struct, most likely, though it can be a slice of interface and be even +// subordinate to another structured variable like a slice of interface, or a +// map of interfaces. Then they can drop this interface in place of the built in +// one and see that they don't have to change the calling code. +// +// Note: though it is not officially recognised as idiomatic, it is the opinion +// of the author of this tutorial that the return values of interface function +// signatures should be named, as it makes no sense to force the developer to +// have to read through the implementation that *idiomatically* should accompany +// an interface, as by idiom, interface should be avoided unless there is more +// than one implementation. +type Codecer interface { + + // Encode takes an arbitrary length byte input and returns the output as + // defined for the codec. + Encode(input []byte) (output string, err error) + + // Decode takes an encoded string and returns if the encoding is valid and + // the value passes any check function defined for the type. + // + // If the check fails or the input is too short to have a check, false and + // nil is returned. This is the contract for this method that + // implementations should uphold. + Decode(input string) (output []byte, err error) +} diff --git a/pkg/crypto/key/pub/public.go b/pkg/crypto/key/pub/public.go index ae31a33b..1e5dba54 100644 --- a/pkg/crypto/key/pub/public.go +++ b/pkg/crypto/key/pub/public.go @@ -4,10 +4,11 @@ package pub import ( + "encoding/base32" "encoding/hex" - + "github.com/decred/dcrd/dcrec/secp256k1/v4" - + "git-indra.lan/indra-labs/indra" "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" @@ -61,6 +62,15 @@ func (pub *Key) ToHex() (s string, e error) { return } +const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +var b32enc = base32.NewEncoding(charset) + +func (pub *Key) ToBase32() (s string) { + b := pub.ToBytes() + return b32enc.EncodeToString(b[:]) +} + func (pb Bytes) Equals(qb Bytes) bool { return pb == qb } func (pub *Key) ToPublicKey() *secp256k1.PublicKey { diff --git a/pkg/relay/handler-intro.go b/pkg/relay/handler-intro.go index 82ed06eb..188c65e6 100644 --- a/pkg/relay/handler-intro.go +++ b/pkg/relay/handler-intro.go @@ -9,5 +9,6 @@ import ( func (eng *Engine) intro(intr *intro.Layer, b slice.Bytes, c *slice.Cursor, prev types.Onion) { - log.D.S(intr) + log.D.F("sending out intro to %s at %s to all known peers", + intr.Key.ToBase32(), intr.AddrPort.String()) } From 859bbecfe285d07ff4f93283c2ce868ce40b7ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D1=85=D0=B5=D1=80=D0=B5=D1=82=D0=B8=D0=BA?= Date: Sun, 26 Feb 2023 10:09:38 +0000 Subject: [PATCH 2/2] improved codec and all alphabet for vanity addresses --- pkg/b32/based32/based32.go | 42 +++++++++++++---- pkg/b32/based32/based32_test.go | 75 +++++++++++++++--------------- pkg/crypto/key/pub/public.go | 20 +++++--- pkg/crypto/key/pub/public_test.go | 27 +++++++++++ pkg/relay/handler-hiddenservice.go | 4 +- 5 files changed, 114 insertions(+), 54 deletions(-) create mode 100644 pkg/crypto/key/pub/public_test.go diff --git a/pkg/b32/based32/based32.go b/pkg/b32/based32/based32.go index 9a15b4c7..52c88c5d 100644 --- a/pkg/b32/based32/based32.go +++ b/pkg/b32/based32/based32.go @@ -14,6 +14,7 @@ import ( "git-indra.lan/indra-labs/indra/pkg/b32/codec" "git-indra.lan/indra-labs/indra/pkg/crypto/sha256" log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" + "git-indra.lan/indra-labs/indra/pkg/util/slice" ) var ( @@ -24,7 +25,7 @@ var ( // charset is the set of characters used in the data section of bech32 strings. // Note that this is ordered, such that for a given charset[i], i is the binary // value of the character. -const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +const charset = "abcdefghijklmnopqrstuvwxyz234569" // Codec provides the encoder/decoder implementation created by makeCodec. var Codec = makeCodec( @@ -43,6 +44,34 @@ func getCutPoint(length, checkLen int) int { return length - checkLen - 1 } +// Shift5bitsLeft allows the elimination of the first 5 bits of the value, +// which are always zero in standard base32 encoding when based on a base 2 +// value. +func Shift5bitsLeft(b slice.Bytes) (o slice.Bytes) { + o = make(slice.Bytes, len(b)) + for i := range b { + if i != len(b)-1 { + o[i] = b[i] << 5 + o[i] += b[i+1] >> 3 + } else { + o[i] = b[i] << 5 + } + } + return +} +func Shift5bitsRight(b slice.Bytes) (o slice.Bytes) { + o = make(slice.Bytes, len(b)) + for i := range b { + if i == 0 { + o[i] = b[i] >> 5 + } else { + o[i] = b[i] >> 5 + o[i] += b[i-1] << 3 + } + } + return +} + func makeCodec( name string, cs string, @@ -68,9 +97,9 @@ func makeCodec( outputBytes[0] = byte(checkLen) copy(outputBytes[1:len(input)+1], input) copy(outputBytes[len(input)+1:], cdc.MakeCheck(input, checkLen)) + outputBytes = Shift5bitsLeft(outputBytes) outputString := enc.EncodeToString(outputBytes) - trimmedString := outputString[1:] - output = cdc.HRP + trimmedString + output = cdc.HRP + outputString[:len(outputString)-1] return } @@ -92,9 +121,7 @@ func makeCodec( payload, checksum := input[1:cutPoint], string(input[cutPoint:]) computedChecksum := string(cdc.MakeCheck(payload, checkLen)) valid := checksum != computedChecksum - if !valid { - e = fmt.Errorf("check failed") } @@ -102,18 +129,17 @@ func makeCodec( } cdc.Decoder = func(input string) (output []byte, e error) { - input = "q" + input[len(cdc.HRP):] + input = input[len(cdc.HRP):] + "q" data := make([]byte, len(input)*5/8) var writtenBytes int writtenBytes, e = enc.Decode(data, []byte(input)) if check(e) { return } - + data = Shift5bitsRight(data) // The first byte signifies the length of the check at the end checkLen := int(data[0]) if writtenBytes < checkLen+1 { - e = fmt.Errorf("check too short") return } diff --git a/pkg/b32/based32/based32_test.go b/pkg/b32/based32/based32_test.go index 6a9cd756..00b046ef 100644 --- a/pkg/b32/based32/based32_test.go +++ b/pkg/b32/based32/based32_test.go @@ -52,7 +52,7 @@ func TestCodec(t *testing.T) { } generated += "}\n" - // t.Log(generated) + t.Log(generated) expected := []string{ "ee94d6cef460b180c995b2f8672e53006aced15fe4d5cc0da332d041feaa1514", @@ -97,43 +97,42 @@ func TestCodec(t *testing.T) { t.FailNow() } } - encodedStr := []string{ - "thff4kw73strqxfjke0seew2vqx4nk3tljdtnqd5vedqs074g23fwfq", - "v8e9yr6f4mwe6ttqshze0tqugmcqwwvjty44jvlw05w4j7a8zneeksz", - "nwq3ysc9585mkry84hpc22y9nukctg2p2v9kar6w37edf8c06ed7sru", - "hapqe4ajxkv8gtwkzxkchh43yll3gxsz5jmkvxdd0nxe635xrtk9zqe", - "elwl9jj0f397eyfu89rwdm3snd25l2vav72lsy37d8acqeu4l3qqsrl", - "f029xn3fq672jhpl425ncgyx63xr8gm3t5sj35dxuqfqwhgw8yvq9cn", - "dqswzlgga30ca4rds8n2pkrmjgw0r7p9tzl8nf78rrww0rddl600me5", - "shpjduxwrfd6akcn70zn55zzwc09cxavuaddww94vnmx3mjefqdjt5j", - "56rzd9gtr9pnnyc3uc2y5ph98wtswj5fckv06eu5cm3zp6eretjkdqg", - "ec9fp6ycwgyvz685q6ae0r6wff5zuh60mqjvpjesv9utpmyd2k8kxxa", - "gf6m0kr0ja7xy0u48yax75gfjk4jz4nvfs4e0cw5f66knffcaagmvdw", - "0l3ghhzexp58zc46vg3xe0yt285cuusurfd8e6sqd4mdwtacuhanry0", - "nulaffu86kysehxxls34lshvmez5950n6v73xvdf4wyeky948z964ua", - "4deeffpq37vqcnp4nrt8h9hc6krgzst8p9yvjv8c7j9lawz0cn952qs", - "elsg50gex3fgv6j8zpezk0u9m59ptppkg6ygnl03teq3zlslxjynhwa", - "gj0umrfu5shhm7lqvjl2t34vul3ed0kw3vjlkpvvy5nr6a2ytpham9a", - "0vfyaw22vgyxvkjptx3f5c39gyxsjl9pay503esanntx3pug39f7jm6", - "spxw3mquglmp3tcpcj3fs4wl7newgrm9kuh7j4lwgyw6wtdp5ph9mhj", - "hdywlljauher99my898vcpckyswyp50evrx938k8cu7ad5v8f0dy0mc", - "62r2ut0y5x795elunrk69pax8l60cw4xmmyg4nztfd4947fpnw9mejv", - "2meqvl40y3psqr9rdm8vyhvulutpr622ft97uh0rnk2wp7g6rlmraew", - "w7528pk6ey8ss3h39gu49rj26vuev50aj43s5022r5qwwngu8hwzxkn", - "jlfggmdqgzfnqn5a4dw86n3nzmlsw0nvs4sfjpmxh3h5j96z0te4n0s", - "5qhmqhmx0g0ruy88gvd3kh6nwzmxhk8ptchzhfln57jq3fj9rjctaxt", - "6tsglvwerm0f848z5hxvfhpcl5wxtpwnhr2vzmvzqctv496h9j6ftjl", - "gnrf6drhayd2h4txf3rk99ny04y6dsrunzleetnhlt7ht3nu602lezx", - "0utcsz7mw4ygglhkfexf8tef9w9e5xm6wwtvpyya8pldwpgkvs0xfsm", - "nv29aa2yqs783mu6pxl3dsrxrzm08fuchxaz4hgd7e6p7e5kq8jghqc", - "4wrs82vguxfn4a7kkt2xkd7xh7ehdz4kzyqx8z9x69ej2xwgj02ga8f", - "6pmw7476nr80ctfsqk7p39kza3rpljwvul69xuqr77mmcl804xvv9sl", - "gftgqnsaxyam32s7a9z5ehkpy5s8lswcp6a72pxzj86jzq24yemxmdp", - "dxmvfvmk92wqpaluklqdfjphva8j76da255glf0d4x7amfldtgzkp4a", + "lxjjvwo6rqldagjswzpqzzokmagvtwrl9snltanumznaqp6vikrjoja", + "mhzfed2jv3oz2llaqxczpla4i3yaoomslevvsm9opuovs65hctzzwqc", + "toareqyfuhu3wdehvxbykkeft4wylikbkmfw5d2or6znjhyp2zn6qd4", + "x5bazv5sgwmhilowcgwyxxvre99rigqcus3wmgnnptgz2rugdlwfcaz", + "z9o9fsspjrf6zej4hfdon3rqtnku9km5m6k9qer6nh5yaz4v9raaqd9", + "jpkfgtrja26ksxb9vkutyieg2rgdhi3rluqsrung4ajaoxiohemafyt", + "naqoc9ii5rpy5vdnqhtkbwd3siopd6bflc9htj6hddoopdnn92pp3zu", + "qxbsn4godjn25wyt6pctuuccoypfyg5m45nnoofvmt3gr3szjanslus", + "u2dcnfildfbtteyr4ykeubxfholqosujywmp2z4uy3rcb2zdzlswnai", + "zyfjb2eyoiemc2hua25zpd2ojjuc4x2p3asmbszqmf4lb3enkwhwgg5", + "ij23pwdps56gep4vhe5g6uijswvscvtmjqvzpyouj22wtjjy55i3mno", + "p9rixxczgbuhcyv2mirgzpelkhuy44q4djnhz2qanv3nol5y4x5tdep", + "t495jj4h2weqzxgg9qrv9qxm3zcufupt2m6rgmnjvoezwefvhcf2v45", + "vnzzjjbar6maytbvtdlhxfxy2wdicqlhbfemsmhy6sf95ocpytfukaq", + "z9qiupizgrjim2shcbzcwp4f3ufblbbwi2eit9prlzarc9q9gsetxo5", + "isp43dj4uqxx369ams9klrvm49rznpworms9wbmmeutd25kelbx53f5", + "pmje5okkmiegmwsblgrjuyrfiegqs9fb5euprzq5ttlgrb4irfj6s32", + "qbgor3a4i93brlybysrjqvo96tzoid3fw4x6sv9oieo2olnbubxf3xs", + "xneo99s54xzdff3ehfhmybyweqoebupzmdgfrhwhy465numhjpnep3y", + "2kdk4lpeug6fuz94tdw2fb5gh92pyovg33eivtcljnvfv6jbtof3zsm", + "k3zam9vperbqadfdn3hmexm494lbd2kkjlf64xpdtwkob6i2d93d5zo", + "o6ukhbw2zehqqrxrfi4vfdsk2m4zmup5svrqupkkduaooti4hxocgwt", + "s9jii3naicjtatu5vnoh2trtc39qoptmqvqjsb3gxrxusf2cplzvtpq", + "uax3ax3gpipd4ehhimnrwx2toc3gxwhblyxcxj9tu6sarjsfdsyl5gl", + "2lqi9mozd3pjhvhcuxgmjxby9uoglbotxdkmc3mcaylmvf2xfs2jls9", + "itdj2ndx5enkxvlgjrdwfftepve2nqd4tc9zzltx9l6xlrt42pk9zcg", + "p4lyqc63oveii9xwjzgjhlzjfofzug32oolmbee5hb9nobiwmqpgjq3", + "tmkf55keaq6hr342bg9rnqdgdc3phj4yxg5cvxin6z2b6zuwahsixay", + "vodqhkmi4gjtv56wwlkgwn6gx6zxncvwceaghcfg2fzskgoispki5hj", + "2b3o6v62tdhpyljqaw6brfwc5rdb9som492fg4ad6633y9hpvgmmfq9", + "ijliatq5ge53rkq65fcuzxwbeuqh9qoyb256kbgcsh2scakvez3g3nb", + "ng3mjm3wfkoab594w9anjsbxm5hs62n5kuui9jpnvg653j9nlicwbv5", } - // encoded := "\nencodedStr := []string{\n" + encoded := "\nencodedStr := []string{\n" // Convert hashes to our base32 encoding format for i := range hashedSeeds { @@ -153,11 +152,11 @@ func TestCodec(t *testing.T) { i, encodedStr[i], encode, ) } - // encoded += "\t\"" + encode + "\",\n" + encoded += "\t\"" + encode + "\",\n" } - // encoded += "}\n" - // t.Log(encoded) + encoded += "}\n" + t.Log(encoded) // Next, decode the encodedStr above, which should be the output of the // original generated seeds, with the index mod 5 truncations performed on diff --git a/pkg/crypto/key/pub/public.go b/pkg/crypto/key/pub/public.go index 1e5dba54..c83db3ff 100644 --- a/pkg/crypto/key/pub/public.go +++ b/pkg/crypto/key/pub/public.go @@ -4,14 +4,15 @@ package pub import ( - "encoding/base32" "encoding/hex" "github.com/decred/dcrd/dcrec/secp256k1/v4" "git-indra.lan/indra-labs/indra" + "git-indra.lan/indra-labs/indra/pkg/b32/based32" "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" log2 "git-indra.lan/indra-labs/indra/pkg/proc/log" + "git-indra.lan/indra-labs/indra/pkg/util/slice" ) var ( @@ -62,13 +63,20 @@ func (pub *Key) ToHex() (s string, e error) { return } -const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" - -var b32enc = base32.NewEncoding(charset) - func (pub *Key) ToBase32() (s string) { b := pub.ToBytes() - return b32enc.EncodeToString(b[:]) + bb := append(b[1:], b[0]) + var e error + if s, e = based32.Codec.Encode(bb); check(e) { + } + return s +} + +func FromBase32(s string) (k *Key, e error) { + var b slice.Bytes + b, e = based32.Codec.Decode(s) + bb := append(b[len(b)-1:], b[:len(b)-1]...) + return FromBytes(bb) } func (pb Bytes) Equals(qb Bytes) bool { return pb == qb } diff --git a/pkg/crypto/key/pub/public_test.go b/pkg/crypto/key/pub/public_test.go new file mode 100644 index 00000000..41834b26 --- /dev/null +++ b/pkg/crypto/key/pub/public_test.go @@ -0,0 +1,27 @@ +package pub + +import ( + "testing" + + "git-indra.lan/indra-labs/indra/pkg/crypto/key/prv" +) + +func TestBase32(t *testing.T) { + for i := 0; i < 1000; i++ { + var k *prv.Key + var e error + if k, e = prv.GenerateKey(); check(e) { + t.Error(e) + t.FailNow() + } + p := Derive(k) + b32 := p.ToBase32() + log.I.Ln(b32) + var kk *Key + kk, e = FromBase32(b32) + if b32 != kk.ToBase32() { + t.Error(e) + t.FailNow() + } + } +} diff --git a/pkg/relay/handler-hiddenservice.go b/pkg/relay/handler-hiddenservice.go index 6cf23e98..3b8dcc37 100644 --- a/pkg/relay/handler-hiddenservice.go +++ b/pkg/relay/handler-hiddenservice.go @@ -9,8 +9,8 @@ import ( func (eng *Engine) hiddenservice(hs *hiddenservice.Layer, b slice.Bytes, c *slice.Cursor, prev types.Onion) { - log.D.F("%s adding introduction for key %x", eng.GetLocalNodeAddress(), - hs.Identity.ToBytes()) + log.D.F("%s adding introduction for key %s", eng.GetLocalNodeAddress(), + hs.Identity.ToBase32()) eng.Introductions.AddIntro(hs.Identity, b[*c:]) log.I.Ln("stored new introduction, starting broadcast") go eng.hiddenserviceBroadcaster(hs.Identity)