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..52c88c5d --- /dev/null +++ b/pkg/b32/based32/based32.go @@ -0,0 +1,154 @@ +// 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" + "git-indra.lan/indra-labs/indra/pkg/util/slice" +) + +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 = "abcdefghijklmnopqrstuvwxyz234569" + +// 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 +} + +// 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, + 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)) + outputBytes = Shift5bitsLeft(outputBytes) + outputString := enc.EncodeToString(outputBytes) + output = cdc.HRP + outputString[:len(outputString)-1] + 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 = 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 + } + 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..00b046ef --- /dev/null +++ b/pkg/b32/based32/based32_test.go @@ -0,0 +1,183 @@ +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{ + "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" + + // 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..c83db3ff 100644 --- a/pkg/crypto/key/pub/public.go +++ b/pkg/crypto/key/pub/public.go @@ -5,12 +5,14 @@ package pub import ( "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 ( @@ -61,6 +63,22 @@ func (pub *Key) ToHex() (s string, e error) { return } +func (pub *Key) ToBase32() (s string) { + b := pub.ToBytes() + 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 } func (pub *Key) ToPublicKey() *secp256k1.PublicKey { 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) 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()) }