Documentation
¶
Overview ¶
Package hh implements Humanized Hash: it turns a blockchain address, a public key or any hash into a small deterministic picture that a person can compare at a glance, a 4 x 4 matrix of solid squares, circles and triangles in four colours. It exists to catch address poisoning and clipboard substitution, which work because people check only the ends of a long string.
A picture is made in three steps:
// Slow and public: cache the 32 bytes per input.
digest, err := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
// One HMAC at most: hh.Universal(digest) or hh.Keyed(digest, key).
fp := hh.Universal(digest)
// Fast.
img, err := hh.Render(fp, 128, hh.RenderOptions{})
The base digest is the only slow value (16 384 iterations of PBKDF2-HMAC-SHA-256, a few milliseconds); it is public, and hosts cache its 32 bytes per input. A universal fingerprint is the same for everyone and is what two parties compare. A keyed fingerprint is one HMAC of the base digest under a 32-byte SecretKey: without the key nobody can compute, and therefore nobody can search for, a lookalike. Inside an application keyed pictures are the default.
An Image holds straight-alpha RGBA pixels; Image.NRGBA hands them to the standard image packages, and Image.EncodePNG, Image.EncodeBMP and Image.EncodeJPEG write files. Fingerprint.Tag gives six characters for a check that is certain where a picture is not, and Fingerprint.Layout describes the cells for hosts that draw vectors themselves.
The algorithm is frozen and has no version. This package follows the specification of hh-cpp, the reference implementation (https://github.com/censync/hh-cpp, docs/SPEC.md), and produces the same fingerprints, pixels and PNG, BMP and JPEG bytes as every other implementation. It uses integer arithmetic only and nothing beyond the standard library.
Every function is total: any input gives a result or one of the Err values of this package, which carry the numeric codes of the specification. Nothing panics. The package keeps no global state and is safe for concurrent use.
Example ¶
The picture of an EVM address that everyone can compute.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
digest, err := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") // slow: cache it
if err != nil {
fmt.Println(err)
return
}
fp := hh.Universal(digest)
img, err := hh.Render(fp, 128, hh.RenderOptions{})
if err != nil {
fmt.Println(err)
return
}
png, err := img.EncodePNG()
if err != nil {
fmt.Println(err)
return
}
tag := fp.Tag()
fmt.Printf("%d x %d pixels, %d bytes of PNG, tag %s-%s\n", img.Width, img.Height, len(png), tag[:3], tag[3:])
}
Output: 128 x 128 pixels, 4360 bytes of PNG, tag TKS-PVH
Example (DigestCache) ¶
The base digest is the slow step and it is public, so a long-running program keeps it per input. The zero value of this cache is ready to use. Bound the map if the inputs come from outside.
package main
import (
"fmt"
"sync"
hh "github.com/censync/go-hh"
)
type digestCache struct {
mu sync.Mutex
digests map[string]hh.BaseDigest // keyed by the decoded input, not by its spelling
}
func (c *digestCache) ofAddress(address []byte) (hh.BaseDigest, error) {
c.mu.Lock()
d, ok := c.digests[string(address)]
c.mu.Unlock()
if ok {
return d, nil
}
d, err := hh.NewBaseDigest(address) // outside the lock: this is the slow call
if err != nil {
return hh.BaseDigest{}, err
}
c.mu.Lock()
if c.digests == nil {
c.digests = make(map[string]hh.BaseDigest)
}
c.digests[string(address)] = d
c.mu.Unlock()
return d, nil
}
// The base digest is the slow step and it is public, so a long-running program
// keeps it per input. The zero value of this cache is ready to use. Bound the
// map if the inputs come from outside.
func main() {
var cache digestCache
address := []byte{
0x5a, 0xAe, 0xb6, 0x05, 0x3F, 0x3E, 0x94, 0xC9, 0xb9, 0xA0,
0x9f, 0x33, 0x66, 0x94, 0x35, 0xE7, 0xEf, 0x1B, 0xeA, 0xed,
}
first, err := cache.ofAddress(address)
if err != nil {
fmt.Println(err)
return
}
again, _ := cache.ofAddress(address)
_, err = cache.ofAddress(nil)
fmt.Println(first == again, len(cache.digests), hh.Universal(first).Tag())
fmt.Println(err)
}
Output: true 1 TKSPVH hh: empty_input: the input has no bytes
Index ¶
Examples ¶
Constants ¶
const ( // DigestSize is the size of a base digest in bytes. DigestSize = 32 // MaxInputSize is the largest input in bytes (SPEC.md section 3). MaxInputSize = 1 << 20 )
const ( MinJPEGQuality = 50 MaxJPEGQuality = 100 DefaultJPEGQuality = 92 )
The range of the JPEG quality and the value hosts should use when they have no reason for another.
const ( // KeySize is the size of a secret key in bytes. KeySize = 32 // KCVSize is the size of a key check value in bytes. KCVSize = 4 )
const ( // MinSize is the smallest render size in pixels. MinSize = 16 // MaxSize is the largest render size in pixels. MaxSize = 1024 )
const FingerprintSize = 32
FingerprintSize is the size of a fingerprint in bytes.
const MaxEncodeDimension = 4096
MaxEncodeDimension is the largest width and height the encoders accept.
const Version = "1.1.0"
Version is the version of this library. The algorithm itself has no version: no release changes a fingerprint, a pixel or an encoded byte.
Variables ¶
var ( // ErrEmptyInput reports an input without bytes. ErrEmptyInput = &Error{CodeEmptyInput, "the input has no bytes"} // ErrInputTooLarge reports an input longer than MaxInputSize bytes. ErrInputTooLarge = &Error{CodeInputTooLarge, "the input is longer than 1048576 bytes"} // ErrInvalidHex reports a string that is not an optional 0x followed by an // even, non-zero number of hexadecimal digits. ErrInvalidHex = &Error{CodeInvalidHex, "the string is not an even number of hexadecimal digits"} // ErrInvalidKey reports a key that is not 32 bytes, is all zero, is nil or // was closed. ErrInvalidKey = &Error{CodeInvalidKey, "the key must be 32 bytes that are not all zero"} // ErrInvalidDigest reports a stored base digest that is not 32 bytes. ErrInvalidDigest = &Error{CodeInvalidDigest, "the base digest must be 32 bytes"} // ErrInvalidFingerprint reports a fingerprint that is not 32 bytes, has an // unknown mode or is the zero value. ErrInvalidFingerprint = &Error{CodeInvalidFingerprint, "the fingerprint must be 32 bytes with a known mode"} // ErrInvalidSize reports a render size outside MinSize..MaxSize, or one // that leaves no room for the cells. ErrInvalidSize = &Error{CodeInvalidSize, "the image size must be 16..1024 and leave room for the cells"} // ErrInvalidFrame reports a frame style that does not fit the shape, such as // FrameTicks on a square picture or FrameRounded on a round one. ErrInvalidFrame = &Error{CodeInvalidFrame, "the frame is not allowed for this shape"} // ErrLowContrast reports an opaque background that is too close to a // palette colour. ErrLowContrast = &Error{CodeLowContrast, "the background is too close to a palette colour"} // ErrInvalidQuality reports a JPEG quality outside 50..100. ErrInvalidQuality = &Error{CodeInvalidQuality, "the JPEG quality must be 50..100"} // ErrInvalidImage reports image dimensions outside 1..4096 or a pixel // buffer of the wrong length. ErrInvalidImage = &Error{CodeInvalidImage, "the image dimensions or its buffer length are invalid"} // ErrInvalidArgument reports an unknown Shape or Frame value, a text that // is not valid UTF-8, or a string that a Parse function does not accept. ErrInvalidArgument = &Error{CodeInvalidArgument, "an unknown enumeration value, an ill-formed text or an unknown name"} )
The errors of this package.
var White = RGB{0xFF, 0xFF, 0xFF}
White is the default background and the usual matte.
Functions ¶
This section is empty.
Types ¶
type Background ¶
type Background struct {
// contains filtered or unexported fields
}
Background is what lies behind the figures: an sRGB colour and an alpha. The zero value is the default, opaque white. Outside rounded or chamfered corners and outside the disc of the round shape a picture is always transparent. Backgrounds are comparable: equal values give equal pictures.
func Opaque ¶
func Opaque(c RGB) Background
Opaque returns an opaque background. Render refuses one that is too close to a palette colour with ErrLowContrast.
func ParseBackground ¶
func ParseBackground(s string) (Background, error)
ParseBackground parses eight hexadecimal digits of either case, "RRGGBBAA". It fails with ErrInvalidArgument.
func Translucent ¶
func Translucent(c RGB, alpha uint8) Background
Translucent returns a background with an alpha from 0 (transparent) to 255 (opaque).
func Transparent ¶
func Transparent() Background
Transparent returns the fully transparent background, for pictures laid over the host's own surface.
func (Background) Alpha ¶
func (b Background) Alpha() uint8
Alpha returns the alpha of the background, 0 (transparent) to 255 (opaque).
func (Background) Color ¶
func (b Background) Color() RGB
Color returns the colour of the background.
func (Background) MarshalText ¶
func (b Background) MarshalText() ([]byte, error)
MarshalText returns the eight digits that String returns. It never fails.
func (Background) String ¶
func (b Background) String() string
String returns the background as eight lowercase hexadecimal digits, "rrggbbaa", the form the golden vectors use.
func (*Background) UnmarshalText ¶
func (b *Background) UnmarshalText(text []byte) error
UnmarshalText is ParseBackground. It fails with ErrInvalidArgument and then leaves the value as it was.
type BaseDigest ¶
type BaseDigest [DigestSize]byte
BaseDigest is the stretched, public 32-byte value of an input (SPEC.md section 4). It is the only slow step, about 16 000 HMAC calls, so hosts cache it per input; both modes and any key derive their fingerprint from it cheaply. It needs no protection.
BaseDigest is an array: it is comparable, can be a map key and is stored as its 32 bytes (d[:]); ImportBaseDigest restores it.
func BaseDigestFromHex ¶
func BaseDigestFromHex(s string) (BaseDigest, error)
BaseDigestFromHex computes the base digest of a binary input given as hexadecimal text: an optional "0x" or "0X", then an even, non-zero number of hexadecimal digits of either case. Every spelling of one address gives the same digest. It fails with ErrInvalidHex, or with ErrInputTooLarge for well-formed text that denotes more than MaxInputSize bytes.
Example ¶
Every spelling of an address gives the same digest, and its 32 bytes are what a host caches.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
a, _ := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
b, _ := hh.BaseDigestFromHex("5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED")
fmt.Println(a == b)
cached := a[:]
restored, _ := hh.ImportBaseDigest(cached)
fmt.Println(restored)
}
Output: true e212927148fcf76f6669c244a0db08bdd4f36dc50a378f6a1a3fe472807e7852
func BaseDigestFromText ¶
func BaseDigestFromText(s string) (BaseDigest, error)
BaseDigestFromText computes the base digest of a text input: the UTF-8 bytes of s, without normalisation. A text input and a binary input with equal bytes give different digests. It fails with ErrEmptyInput or ErrInputTooLarge, and after the length check with ErrInvalidArgument if s is not valid UTF-8: such a string is not the encoding of any text, and hashing it would give a picture that no other implementation shows for the text it was meant to be.
Example ¶
Formats that exist only as text, such as Bitcoin addresses, are hashed as text.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
digest, err := hh.BaseDigestFromText("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
fmt.Println(digest, err)
}
Output: dc705192e4a205d8c403ae7693290df45f09cec04116ad38f6140f349392548f <nil>
func BaseDigestFromUTF8 ¶
func BaseDigestFromUTF8(text []byte) (BaseDigest, error)
BaseDigestFromUTF8 computes the base digest of a text input from its UTF-8 bytes, taken verbatim and not validated (SPEC.md section 3). It fails with ErrEmptyInput or ErrInputTooLarge.
func ImportBaseDigest ¶
func ImportBaseDigest(b []byte) (BaseDigest, error)
ImportBaseDigest restores a cached digest from its 32 bytes. It fails with ErrInvalidDigest for any other length.
func NewBaseDigest ¶
func NewBaseDigest(data []byte) (BaseDigest, error)
NewBaseDigest computes the base digest of a binary input: the bytes of an address, a public key or a hash, 1 to MaxInputSize of them. It fails with ErrEmptyInput or ErrInputTooLarge.
func (BaseDigest) String ¶
func (d BaseDigest) String() string
String returns the digest as 64 lowercase hexadecimal digits.
type Cell ¶
type Cell struct {
// Figure is what the cell shows.
Figure Figure
// Color is the index of the figure's colour in Layout.Palette, 0..3. It
// is 0 for an empty cell.
Color uint8
}
Cell is one cell of the 4 x 4 matrix.
type Code ¶
type Code int
Code is the numeric value of an error condition. The values are those of the C ABI of hh-cpp (SPEC.md section 14) and are the same in every implementation.
const ( CodeOK Code = 0 CodeEmptyInput Code = 1 CodeInputTooLarge Code = 2 CodeInvalidHex Code = 3 CodeInvalidKey Code = 4 CodeInvalidDigest Code = 5 CodeInvalidFingerprint Code = 6 CodeInvalidSize Code = 7 CodeInvalidFrame Code = 8 CodeLowContrast Code = 9 CodeInvalidQuality Code = 10 CodeInvalidImage Code = 11 CodeBufferTooSmall Code = 12 CodeOutOfMemory Code = 13 CodeInvalidArgument Code = 14 )
The error codes of SPEC.md section 14. CodeBufferTooSmall and CodeOutOfMemory exist for completeness of the table: this package allocates its results itself and never reports them.
type ContrastReport ¶
type ContrastReport struct {
// FiguresX100 is the contrast of the weakest palette colour against the
// background.
FiguresX100 int
// FrameX100 is the contrast of the frame against the background. It is
// informative.
FrameX100 int
}
ContrastReport holds WCAG contrast ratios times 100, rounded down: 300 means 3:1.
func MeasureContrast ¶
func MeasureContrast(opts RenderOptions, page RGB) ContrastReport
MeasureContrast measures what the options give over a page of the colour page; for an opaque background the page does not matter. Render refuses an opaque background with FiguresX100 below 200; hosts should warn below 300. For a translucent background pass the colour of the surface underneath.
Example ¶
Figures that vanish into the background make different pictures look alike, so a host that lets users choose a background measures it first.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
page := hh.RGB{R: 0x12, G: 0x12, B: 0x12} // what the host paints underneath
for _, background := range []hh.Background{
hh.Transparent(),
hh.Opaque(hh.RGB{R: 0xF2, G: 0xF2, B: 0xF2}),
hh.Opaque(hh.RGB{R: 0x9E, G: 0x9E, B: 0x9E}),
} {
report := hh.MeasureContrast(hh.RenderOptions{Background: background}, page)
switch {
case report.FiguresX100 < 200:
fmt.Printf("%v: %d, Render refuses it if it is opaque\n", background, report.FiguresX100)
case report.FiguresX100 < 300:
fmt.Printf("%v: %d, warn the user\n", background, report.FiguresX100)
default:
fmt.Printf("%v: %d\n", background, report.FiguresX100)
}
}
}
Output: 00000000: 300 f2f2f2ff: 268, warn the user 9e9e9eff: 112, Render refuses it if it is opaque
type Error ¶
type Error struct {
// contains filtered or unexported fields
}
Error is the type of every error this package returns. The package returns only its Err values, never a copy or a wrapped one, so errors can be compared with == or errors.Is, and errors.As gives access to the numeric code.
Example ¶
Errors are values: compare them, or read the numeric code that every implementation of hh shares.
package main
import (
"errors"
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
_, err := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAe") // one digit short
fmt.Println(errors.Is(err, hh.ErrInvalidHex))
var e *hh.Error
if errors.As(err, &e) {
fmt.Println(int(e.Code()), e.Code())
}
}
Output: true 3 invalid_hex
type Figure ¶
type Figure uint8
Figure is what a cell shows. The values are the layout values of SPEC.md section 5.1.
const ( FigureNone Figure = 0 // an empty cell FigureSquare Figure = 1 // the full cell FigureCircle Figure = 2 // the disc inscribed in the cell FigureTriangleUp Figure = 3 // base on the bottom side FigureTriangleRight Figure = 4 // base on the left side FigureTriangleDown Figure = 5 // base on the top side FigureTriangleLeft Figure = 6 // base on the right side )
The figures of SPEC.md section 5.1. A triangle is isosceles: its base is one full side of the cell and its apex the midpoint of the opposite side.
type Fingerprint ¶
type Fingerprint struct {
// contains filtered or unexported fields
}
Fingerprint is 32 bytes and the mode they were derived in: everything a picture depends on. It is comparable and can be a map key. The zero value is not a fingerprint: Render refuses it with ErrInvalidFingerprint.
func ImportFingerprint ¶
func ImportFingerprint(b []byte, mode Mode) (Fingerprint, error)
ImportFingerprint is for hosts that compute the keyed HMAC elsewhere, for example inside a secure element: it takes the 32 fingerprint bytes and the mode they belong to. It fails with ErrInvalidFingerprint for any other length or an unknown mode.
Example ¶
A host that computes the keyed HMAC elsewhere, for example in a secure element, imports the result and never creates a SecretKey.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
fromElsewhere := make([]byte, hh.FingerprintSize)
fp, err := hh.ImportFingerprint(fromElsewhere, hh.ModeKeyed)
fmt.Println(fp.Mode(), fp.Tag(), err)
_, err = hh.ImportFingerprint(fromElsewhere[:16], hh.ModeKeyed)
fmt.Println(err)
}
Output: keyed 000000 <nil> hh: invalid_fingerprint: the fingerprint must be 32 bytes with a known mode
func Keyed ¶
func Keyed(d BaseDigest, key *SecretKey) (Fingerprint, error)
Keyed returns the keyed fingerprint: one HMAC of the base digest under the key. It fails with ErrInvalidKey if the key is nil or was closed.
Example ¶
The private picture: only holders of the key can compute it.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
// The key of the examples. A real key is uniformly random or the output of a
// key derivation function, and never appears in source code.
var exampleKey = []byte("an example key, 32 bytes long...")
func main() {
key, err := hh.NewSecretKey(exampleKey)
if err != nil {
fmt.Println(err)
return
}
defer key.Close()
digest, _ := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
fp, err := hh.Keyed(digest, key)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%v %s, key check value %x\n", fp.Mode(), fp.Tag(), key.KCV())
}
Output: keyed 1KKSCS, key check value 6ae70605
func Universal ¶
func Universal(d BaseDigest) Fingerprint
Universal returns the universal fingerprint, which is the base digest itself.
func (Fingerprint) Bytes ¶
func (fp Fingerprint) Bytes() [FingerprintSize]byte
Bytes returns the 32 bytes.
func (Fingerprint) IsZero ¶
func (fp Fingerprint) IsZero() bool
IsZero reports whether fp is the zero value, which no function of this package returns without an error.
func (Fingerprint) Layout ¶
func (fp Fingerprint) Layout() Layout
Layout returns the cells, the palette and the mode. The zero Fingerprint gives 16 empty cells.
Example ¶
A host that draws vectors itself reads the cells instead of the pixels.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
digest, _ := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
layout := hh.Universal(digest).Layout()
for i, cell := range layout.Cells {
if cell.Figure == hh.FigureNone {
fmt.Printf("[%21s]", "")
} else {
fmt.Printf("[%-14v %v]", cell.Figure, layout.Palette[cell.Color])
}
if i%4 == 3 {
fmt.Println()
}
}
}
Output: [triangle_left 7a96c5][ ][triangle_up c10445][circle c10445] [square 890af0][triangle_left d48200][triangle_left c10445][circle 890af0] [circle 7a96c5][circle 890af0][triangle_down 7a96c5][square 7a96c5] [triangle_right 7a96c5][triangle_down d48200][ ][triangle_right d48200]
func (Fingerprint) Mode ¶
func (fp Fingerprint) Mode() Mode
Mode returns the mode of the fingerprint, or 0 for the zero value.
func (Fingerprint) Tag ¶
func (fp Fingerprint) Tag() string
Tag returns the six-character Crockford Base32 tag, for example "K7QM2X"; hosts display it grouped as "K7Q-M2X". Text allows a certain check where a picture does not. The tag of a keyed fingerprint depends on the key.
type Frame ¶
type Frame uint8
Frame is the frame style of a picture. Every style is open to universal and keyed fingerprints alike; what limits it is the shape. FrameNone, FramePlain, FrameDouble and FrameThick fit both shapes, FrameRounded, FrameChamfered and FrameBrackets need ShapeSquare, FrameTicks and FrameGaps need ShapeRound, and Render refuses a style that does not fit the shape with ErrInvalidFrame. Only FrameAutomatic looks at the mode: it gives keyed square pictures rounded corners. A host that marks its keyed pictures with a frame picks the style.
const ( FrameAutomatic Frame = 0 // keyed and square: FrameRounded; otherwise FrameNone FrameNone Frame = 1 // either shape: no frame FramePlain Frame = 2 // either shape: a thin square frame, or a thin ring FrameRounded Frame = 3 // square only: rounded corners FrameChamfered Frame = 4 // square only: four cut corners FrameDouble Frame = 5 // either shape: two thin lines FrameThick Frame = 6 // either shape: one line three times as thick FrameBrackets Frame = 7 // square only: corner brackets FrameTicks Frame = 8 // round only: a ring with four ticks FrameGaps Frame = 9 // round only: a ring with four gaps )
The frame styles of SPEC.md section 6, each open to both modes.
func ParseFrame ¶
ParseFrame is the inverse of Frame.String. It fails with ErrInvalidArgument.
func (Frame) MarshalText ¶
MarshalText returns the name that String returns. It fails with ErrInvalidArgument for a value outside the table.
func (Frame) String ¶
String returns the name of SPEC.md section 6, for example "double", or "invalid" for a value outside the table.
func (*Frame) UnmarshalText ¶
UnmarshalText is ParseFrame. It fails with ErrInvalidArgument and then leaves the value as it was.
type Image ¶
type Image struct {
// Width and Height are the dimensions in pixels.
Width, Height int
// Pix holds the pixels: R, G, B, A of the top left pixel, then the rest
// of its row, then the rows below.
Pix []byte
}
Image is pixels, not a picture: Width x Height pixels, row-major from the top left, 4 bytes per pixel in the order R, G, B, A with straight (non-premultiplied) alpha, the layout of image.NRGBA.
Render returns square images. The encoders take any Image of 1..4096 by 1..4096 pixels whose Pix has Width * Height * 4 bytes and fail with ErrInvalidImage otherwise. They are deterministic: the same image gives the same bytes in every implementation of hh.
func Render ¶
func Render(fp Fingerprint, size int, opts RenderOptions) (*Image, error)
Render draws the fingerprint as size x size pixels. The checks are made in the order of SPEC.md section 6: ErrInvalidFingerprint for the zero Fingerprint, ErrInvalidArgument for an unknown Shape or Frame value, ErrInvalidSize unless size is MinSize..MaxSize, ErrInvalidFrame if the frame does not fit the shape (the mode plays no part), ErrLowContrast for an opaque background with less than 2:1 against a palette colour, and ErrInvalidSize again if the size leaves no room for the cells (the round shape with FrameDouble or FrameThick below 18 pixels).
Render at the exact device-pixel size instead of scaling a picture: the rasteriser is anti-aliased for the size it is asked for.
Example ¶
A round picture with a double frame on a dark surface. Every style that fits the shape is open to both modes; one that does not is refused.
package main
import (
"fmt"
hh "github.com/censync/go-hh"
)
// The key of the examples. A real key is uniformly random or the output of a
// key derivation function, and never appears in source code.
var exampleKey = []byte("an example key, 32 bytes long...")
func main() {
key, _ := hh.NewSecretKey(exampleKey)
defer key.Close()
digest, _ := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
fp, _ := hh.Keyed(digest, key)
opts := hh.RenderOptions{
Shape: hh.ShapeRound,
Frame: hh.FrameDouble,
Background: hh.Opaque(hh.RGB{R: 0x12, G: 0x12, B: 0x12}),
}
img, err := hh.Render(fp, 96, opts)
fmt.Println(img.Width, img.Height, len(img.Pix), err)
// The same look for the public picture.
img, err = hh.Render(hh.Universal(digest), 96, opts)
fmt.Println(img.Width, img.Height, len(img.Pix), err)
// Rounded corners need the square shape.
opts.Frame = hh.FrameRounded
_, err = hh.Render(fp, 96, opts)
fmt.Println(err)
}
Output: 96 96 36864 <nil> 96 96 36864 <nil> hh: invalid_frame: the frame is not allowed for this shape
func (*Image) EncodeBMP ¶
EncodeBMP encodes the image as a 24-bit BMP (SPEC.md section 12). BMP carries no alpha here, so every pixel is laid over the matte; White is the usual one. It fails with ErrInvalidImage.
func (*Image) EncodeJPEG ¶
EncodeJPEG encodes the image as baseline JFIF, 8 bits per sample, 4:4:4, with the tables of ITU-T T.81 annex K (SPEC.md section 13). JPEG has no alpha, so every pixel is laid over the matte; White is the usual one. It fails with ErrInvalidImage, then with ErrInvalidQuality unless quality is 50..100.
JPEG is offered for compatibility; it rings on flat colour edges. Prefer PNG or the pixels themselves.
func (*Image) EncodePNG ¶
EncodePNG encodes the image as an 8-bit truecolour PNG, with an alpha channel only if some pixel is not opaque (SPEC.md section 11). It fails with ErrInvalidImage.
func (*Image) NRGBA ¶
NRGBA returns the image as an *image.NRGBA for the standard image packages. The result shares Pix with m. An Image that the encoders would refuse gives an empty image.
Example ¶
The pixels have the layout of image.NRGBA, so the standard image packages take them as they are.
package main
import (
"fmt"
"image"
"image/draw"
hh "github.com/censync/go-hh"
)
func main() {
digest, _ := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
img, _ := hh.Render(hh.Universal(digest), 64, hh.RenderOptions{Background: hh.Transparent()})
canvas := image.NewRGBA(image.Rect(0, 0, 200, 80))
draw.Draw(canvas, image.Rect(8, 8, 72, 72), img.NRGBA(), image.Point{}, draw.Over)
fmt.Println(canvas.RGBAAt(8+10, 8+10), canvas.RGBAAt(8+31, 8+31))
}
Output: {122 150 197 255} {0 0 0 0}
type Layout ¶
type Layout struct {
// Mode is the mode of the fingerprint.
Mode Mode
// Cells are the 16 cells, row-major from the top left.
Cells [16]Cell
// Palette holds the four colours of the figures.
Palette [4]RGB
// FrameColor is the colour of the frame.
FrameColor RGB
}
Layout is what a fingerprint shows, for hosts that draw vectors themselves. Cells are row-major from the top left. The raster of Render is the canonical form and the only one covered by byte-exact vectors.
type Mode ¶
type Mode uint8
Mode tells how a fingerprint was derived. Universal pictures are the same for everyone; keyed pictures can be computed only with the secret key. The two pictures of one input are unrelated.
func ParseMode ¶
ParseMode is the inverse of Mode.String: it takes "universal" or "keyed" and fails with ErrInvalidArgument.
func (Mode) MarshalText ¶
MarshalText returns the name that String returns, for a host that stores the mode beside the bytes of a fingerprint. It fails with ErrInvalidArgument for any value other than ModeUniversal and ModeKeyed.
func (*Mode) UnmarshalText ¶
UnmarshalText is ParseMode. It fails with ErrInvalidArgument and then leaves the value as it was.
type Opacity ¶
type Opacity struct {
// contains filtered or unexported fields
}
Opacity is an alpha value whose zero value is fully opaque; Alpha makes any other.
func ParseOpacity ¶
ParseOpacity parses an alpha, 0 to 255, written as ASCII decimal digits without a sign. It fails with ErrInvalidArgument.
func (Opacity) MarshalText ¶
MarshalText returns the number that String returns. It never fails.
func (*Opacity) UnmarshalText ¶
UnmarshalText is ParseOpacity. It fails with ErrInvalidArgument and then leaves the value as it was.
type RGB ¶
type RGB struct {
// R, G and B are the red, green and blue channels.
R, G, B uint8
}
RGB is an sRGB colour.
func ParseRGB ¶
ParseRGB parses six hexadecimal digits of either case, "RRGGBB". It fails with ErrInvalidArgument.
func (RGB) MarshalText ¶
MarshalText returns the six digits that String returns. It never fails.
func (*RGB) UnmarshalText ¶
UnmarshalText is ParseRGB. It fails with ErrInvalidArgument and then leaves the value as it was.
type RenderOptions ¶
type RenderOptions struct {
// Shape is square or round.
Shape Shape
// Frame is the frame style: any style that fits the shape, in either mode.
// A host that marks its keyed pictures with a frame uses one style
// everywhere, since a marker is only useful if it is familiar, and names
// the mode in the caption, since a frame alone proves nothing.
Frame Frame
// Background is the colour and alpha behind the figures.
Background Background
// FrameAlpha is the alpha of the frame; the frame colour itself is fixed.
FrameAlpha Opacity
}
RenderOptions chooses the look of a render; the cells, the palette and the geometry are fixed by the specification. The zero value is the default: a square picture on opaque white with the automatic frame.
Every field is an encoding.TextMarshaler and an encoding.TextUnmarshaler in the text form of its String method and its Parse function, so the options pass unchanged through encoding/json and every other format that is built on those interfaces:
{"Shape":"round","Frame":"double","Background":"121212ff","FrameAlpha":"200"}
A field that is missing keeps its default. A text that the Parse function of the field refuses is ErrInvalidArgument.
Example ¶
The look of a program in its configuration file. Every field of RenderOptions reads and writes the names and the hexadecimal forms of the specification.
package main
import (
"encoding/json"
"errors"
"fmt"
hh "github.com/censync/go-hh"
)
func main() {
type config struct {
PictureSize int
Picture hh.RenderOptions
}
file := []byte(`{"PictureSize": 96, "Picture": {"Shape": "round", "Frame": "double", "Background": "121212ff"}}`)
var c config
if err := json.Unmarshal(file, &c); err != nil {
fmt.Println(err)
return
}
fmt.Println(c.Picture.Shape, c.Picture.Frame, c.Picture.Background, c.Picture.FrameAlpha)
written, _ := json.Marshal(c.Picture)
fmt.Println(string(written))
err := json.Unmarshal([]byte(`{"Picture": {"Frame": "dotted"}}`), &c)
fmt.Println(errors.Is(err, hh.ErrInvalidArgument))
}
Output: round double 121212ff 255 {"Shape":"round","Frame":"double","Background":"121212ff","FrameAlpha":"255"} true
type SecretKey ¶
type SecretKey struct {
// contains filtered or unexported fields
}
SecretKey is the 32-byte secret of keyed mode. The key must be uniformly random or the output of a key derivation function; there is no passphrase form.
Close overwrites the key with zeros. That is as far as a garbage-collected runtime lets a library go: the copies that crypto/hmac makes while a fingerprint is computed, and any copy the runtime made when it moved a stack, stay in memory until they are reused. A host that must keep the key out of the Go heap computes the keyed HMAC elsewhere and uses ImportFingerprint.
A SecretKey is safe for concurrent use, including Close. Pass the pointer. A copy of the struct is a second handle on the same key, not a second key: closing either closes both.
No printer shows the bytes. A *SecretKey prints as a placeholder through Format and String. A SecretKey value, which has neither method, prints as the address of a function, and so does a key in an unexported field of another struct. Packages that dump values by reflection, unexported fields included, find the same function value and nothing behind it, and encoding/json writes {}.
func NewSecretKey ¶
NewSecretKey accepts exactly 32 bytes that are not all zero; anything else is ErrInvalidKey. A zero-filled buffer is what a failed key load looks like and must never produce pictures. The bytes are copied: wipe your own slice.
func (*SecretKey) Close ¶
Close overwrites the key with zeros; every later use of the key fails with ErrInvalidKey, except KCV. Closing twice, or closing a nil key, is harmless. The error is always nil; the signature is that of io.Closer.
func (*SecretKey) Format ¶
Format prints a placeholder for every verb that fmt hands to a Formatter, so that a key never reaches a log through the fmt package. The verbs %T and %p, which fmt answers itself, give the type and the address.
func (*SecretKey) KCV ¶
KCV returns the key check value: 4 bytes a host stores beside its cached data to notice that the key, and with it every keyed picture, changed. It is public: it reveals nothing useful about the key, it is computed when the key is created and it stays available after Close. The KCV of a nil key is all zero.
type Shape ¶
type Shape uint8
Shape is the outline of the picture.
func ParseShape ¶
ParseShape is the inverse of Shape.String. It fails with ErrInvalidArgument.
func (Shape) MarshalText ¶
MarshalText returns the name that String returns, so that a Shape is a name in JSON and in every other format that knows encoding.TextMarshaler. It fails with ErrInvalidArgument for a value outside the table.
func (Shape) String ¶
String returns the name of SPEC.md section 6, "square" or "round", or "invalid" for any other value.
func (*Shape) UnmarshalText ¶
UnmarshalText is ParseShape. It fails with ErrInvalidArgument and then leaves the value as it was.












