Merge branch 'protocol'
This commit is contained in:
1
go.mod
1
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
|
||||
|
||||
1
go.sum
1
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=
|
||||
|
||||
154
pkg/b32/based32/based32.go
Normal file
154
pkg/b32/based32/based32.go
Normal file
@@ -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
|
||||
}
|
||||
183
pkg/b32/based32/based32_test.go
Normal file
183
pkg/b32/based32/based32_test.go
Normal file
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
102
pkg/b32/codec/types.go
Normal file
102
pkg/b32/codec/types.go
Normal file
@@ -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) }
|
||||
73
pkg/b32/codecer/interface.go
Normal file
73
pkg/b32/codecer/interface.go
Normal file
@@ -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 <thing
|
||||
// it does>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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
27
pkg/crypto/key/pub/public_test.go
Normal file
27
pkg/crypto/key/pub/public_test.go
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user