The mutation corpus of spec 64 gains format 3: 209 cases, 169 of the spec. - The 33 mutations of the first two lists on format3_single and format3_time_and_key_portable, named "format 3: ...", with a sibling written by EncryptFiles and built capsules that hold a BODY. - The list of format 3, 47 cases: one for each value of a line with several (AREA_LEN 0, 511, 513 and 66048; SECURITY_LEN 0 and 513; HEAD_LEN 0 and 2^24 + 1), and the three that open without a code, with the verdicts X, F1 and S1. VERSION 4 is "format 3: version changed", as in format 2. Each seals BODY again with FK_PAYLOAD and the nonce of its fixture, and the control with the new L when L changes; the two that need a head followed by another STREAM chunk derive from format3_tree, whose comment takes the bytes the path loses so that only the head and its chunk change. - Further cases: format 3 relabeled 1, and time_and_key relabeled 2 with the identity and with the .dkk. - A mutation may expect the capsule to open with its verdicts; the exported case records them, with the result ok at step 0. - Splice gives an edit for each run of changed bytes, runs closer than 16 bytes merged, and one more for what one side has beyond the other: a head sealed again changes its bytes and the tag of its chunk, 64 KiB apart. Earlier cases are written with more edits and give the same capsules. The corpus is 706 KB, 476 KB of them the capsule of 65536 implicit folders, whose head is 235 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>v0.10
parent
db862d5524
commit
18eb3c3f48
@ -0,0 +1,405 @@
|
||||
package testkit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
datekeys "g.activething.com/go/DateKeys"
|
||||
"g.activething.com/go/DateKeys/agewrap"
|
||||
"g.activething.com/go/DateKeys/capsule"
|
||||
"g.activething.com/go/DateKeys/internal/cbortest"
|
||||
)
|
||||
|
||||
// The mutations of format 3 (spec §64): BODY, security and the head of the
|
||||
// fixtures format3_single and format3_tree, sealed again as whoever knows
|
||||
// I_PAYLOAD can, and their control when L changes, as anyone can seal the
|
||||
// control of a time_only capsule (spec §36.1).
|
||||
|
||||
// Format3SpecMutations is the number of mutations of the list of format 3
|
||||
// of spec §64 in the corpus, besides VERSION 4, which is "format 3: version
|
||||
// changed": one for each value of a line with several, and the three cases
|
||||
// that open with their verdict.
|
||||
const Format3SpecMutations = 47
|
||||
|
||||
// resealPayload returns PAYLOAD_AGE of f with its plaintext replaced,
|
||||
// sealed with FK_PAYLOAD and the nonce of the fixture.
|
||||
func (f *LoadedFixture) resealPayload(plaintext []byte) ([]byte, error) {
|
||||
raw, err := hex.DecodeString(f.PayloadIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := agewrap.NewPayloadIdentity(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stanzas, err := agewrap.Stanzas(bytes.NewReader(f.Parts.Payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fk, err := id.Unwrap(stanzas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer clear(fk)
|
||||
return ResealAge(f.Parts.Payload, fk, plaintext)
|
||||
}
|
||||
|
||||
// WithBody returns f, a time_only fixture of format 3 or 2, with the content
|
||||
// of PAYLOAD_AGE replaced by body and followed by the zeros of its padding
|
||||
// up to P = rule(len(body)); plain, when not nil, edits that plaintext. When
|
||||
// L changes, the control is sealed again with the new L (spec §29.1, §29.2).
|
||||
func (f *LoadedFixture) WithBody(body []byte, plain func(p []byte) []byte) (*MutationInput, error) {
|
||||
l := uint64(len(body))
|
||||
p, err := capsule.PaddedLength(l, capsule.Padding(f.Padding))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plaintext := append(bytes.Clone(body), make([]byte, p-l)...)
|
||||
if plain != nil {
|
||||
plaintext = plain(plaintext)
|
||||
}
|
||||
parts := f.Parts
|
||||
if l != f.PayloadLength {
|
||||
in, err := f.WithControl(func(c map[uint64]any) { c[6] = payloadLength(l) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parts, err = Split(in.DKC); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
payload, err := f.resealPayload(plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.input(Join(parts.Prelude, parts.Header, parts.Sealed, payload)), nil
|
||||
}
|
||||
|
||||
// body3 is BODY split into its parts (spec §29.2).
|
||||
type body3 struct {
|
||||
frame capsule.BodyFrame
|
||||
area []byte
|
||||
head []byte
|
||||
content []byte
|
||||
}
|
||||
|
||||
// readBody3 returns the BODY of the format 3 fixture f, from its plaintext
|
||||
// file in dir, split.
|
||||
func readBody3(f *LoadedFixture, dir string) (body3, error) {
|
||||
b, err := os.ReadFile(filepath.Join(dir, f.PlaintextFile))
|
||||
if err != nil {
|
||||
return body3{}, err
|
||||
}
|
||||
frame, err := capsule.ParseBodyFrame(b[:capsule.BodyFrameSize], uint64(len(b)))
|
||||
if err != nil {
|
||||
return body3{}, err
|
||||
}
|
||||
a := capsule.BodyFrameSize + int(frame.AreaLen)
|
||||
h := a + int(frame.HeadLen)
|
||||
return body3{frame: frame, area: b[capsule.BodyFrameSize:a], head: b[a:h], content: b[h:]}, nil
|
||||
}
|
||||
|
||||
// bytes joins the parts again, with the frame as it is.
|
||||
func (b body3) bytes() []byte {
|
||||
fb := b.frame.Bytes()
|
||||
return Join(fb[:], b.area, b.head, b.content)
|
||||
}
|
||||
|
||||
// withHeadBytes returns BODY with its head replaced, HEAD_LEN following it.
|
||||
func (b body3) withHeadBytes(head []byte) []byte {
|
||||
b.head, b.frame.HeadLen = head, uint32(len(head))
|
||||
return b.bytes()
|
||||
}
|
||||
|
||||
// body3Edit returns the format 3 fixture f with edit applied to its BODY,
|
||||
// and plain, when not nil, to the plaintext of PAYLOAD_AGE.
|
||||
func (e *MutationEnv) body3Edit(f *LoadedFixture, edit func(b body3) []byte, plain func(p []byte) []byte) (*MutationInput, error) {
|
||||
b, err := readBody3(f, e.Dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.WithBody(edit(b), plain)
|
||||
}
|
||||
|
||||
// headEdit returns the format 3 fixture f with its head decoded as a map,
|
||||
// edited and encoded again.
|
||||
func (e *MutationEnv) headEdit(f *LoadedFixture, edit func(h map[uint64]any)) (*MutationInput, error) {
|
||||
return e.body3Edit(f, func(b body3) []byte {
|
||||
m, err := cbortest.UnmarshalMap(b.head)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
edit(m)
|
||||
return b.withHeadBytes(mustMarshal(m))
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// entry is a file entry of a head, of the content of format3_single when
|
||||
// its size is not 0: the SHA-256 is that of its size first bytes.
|
||||
func entry(path any, size, start, end uint64) map[uint64]any {
|
||||
sum := sha256.Sum256([]byte(single3Content[:min(size, uint64(len(single3Content)))]))
|
||||
return map[uint64]any{0: path, 1: size, 2: start, 3: end, 4: sum[:]}
|
||||
}
|
||||
|
||||
// single3Content is the content of the only file of format3_single.
|
||||
const single3Content = "Hola desde el pasado.\n"
|
||||
|
||||
// file0 sets key k of the first file of the head h to v.
|
||||
func file0(h map[uint64]any, k uint64, v any) {
|
||||
h[5].([]any)[0].(map[uint64]any)[k] = v
|
||||
}
|
||||
|
||||
// securityV2 is SECURITY_CBOR of version 2.
|
||||
func securityV2() []byte {
|
||||
return mustMarshal(map[uint64]any{0: capsule.SecurityTypeTag, 1: uint64(2)})
|
||||
}
|
||||
|
||||
// implicitFolders returns the entries of files of 0 bytes whose paths make
|
||||
// n implicit folders: 31 for each path, "a3k/0/0/…/0/f", and the rest in a
|
||||
// last one (spec §29.5, R9).
|
||||
func implicitFolders(n int) []any {
|
||||
var paths []string
|
||||
for i := 0; n > 0; i++ {
|
||||
depth := min(n, 31)
|
||||
seg := []string{fmt.Sprintf("%04d", i)}
|
||||
for range depth - 1 {
|
||||
seg = append(seg, "0")
|
||||
}
|
||||
paths = append(paths, strings.Join(append(seg, "f"), "/"))
|
||||
n -= depth
|
||||
}
|
||||
var out []any
|
||||
for _, p := range paths {
|
||||
out = append(out, entry(p, 0, 0, 0))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// format3Mutations returns the mutations of the list of format 3 of spec
|
||||
// §64, except VERSION 4, which is "format 3: version changed", and further
|
||||
// cases of their own. Each derives from format3_single, or from
|
||||
// format3_tree when it needs a head followed by another STREAM chunk.
|
||||
func format3Mutations() []Mutation {
|
||||
hi, nc, integ := datekeys.ErrHeadInvalid, datekeys.ErrNonCanonicalCBOR, datekeys.ErrIntegrity
|
||||
ok := func(in *MutationInput) (*MutationInput, error) { return in, nil }
|
||||
spec := func(name string, want *datekeys.Error, mk func(e *MutationEnv) (*MutationInput, error)) Mutation {
|
||||
return Mutation{Name: name, Spec: true, Want: want, Step: 17, Network: true, Make: mk}
|
||||
}
|
||||
frame := func(at int, v uint32) func(e *MutationEnv) (*MutationInput, error) {
|
||||
return func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte {
|
||||
out := b.bytes()
|
||||
binary.BigEndian.PutUint32(out[at:], v)
|
||||
return out
|
||||
}, nil)
|
||||
}
|
||||
}
|
||||
head := func(edit func(h map[uint64]any)) func(e *MutationEnv) (*MutationInput, error) {
|
||||
return func(e *MutationEnv) (*MutationInput, error) { return e.headEdit(e.TimeOnly3, edit) }
|
||||
}
|
||||
headBytes := func(edit func(h []byte) []byte) func(e *MutationEnv) (*MutationInput, error) {
|
||||
return func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte { return b.withHeadBytes(edit(bytes.Clone(b.head))) }, nil)
|
||||
}
|
||||
}
|
||||
path := func(p string) func(e *MutationEnv) (*MutationInput, error) {
|
||||
return head(func(h map[uint64]any) { file0(h, 0, p) })
|
||||
}
|
||||
// The single file of format3_single, 22 bytes, then one of 0 bytes.
|
||||
two := func(a, b string) func(e *MutationEnv) (*MutationInput, error) {
|
||||
return head(func(h map[uint64]any) { h[5] = []any{entry(a, 22, 0, 22), entry(b, 0, 22, 22)} })
|
||||
}
|
||||
security := func(sec []byte, v capsule.Verdicts) Mutation {
|
||||
return Mutation{Spec: true, Network: true, Verdicts: &v, Make: func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte {
|
||||
b.area = append(bytes.Clone(sec), make([]byte, len(b.area)-len(sec))...)
|
||||
b.frame.SecurityLen = uint32(len(sec))
|
||||
return b.bytes()
|
||||
}, nil)
|
||||
}}
|
||||
}
|
||||
lastPadding := func(p []byte) []byte { p[len(p)-1] = 1; return p }
|
||||
// The first path of format3_tree, carta.txt, becomes "..", which keeps
|
||||
// the byte order of the paths, and the comment grows by the 7 bytes the
|
||||
// path loses, so that the head keeps its length and only its bytes
|
||||
// change; it is in the first STREAM chunk.
|
||||
treeDotDot := func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.headEdit(e.Tree3, func(h map[uint64]any) {
|
||||
old := h[5].([]any)[0].(map[uint64]any)[0].(string)
|
||||
file0(h, 0, "..")
|
||||
h[3] = h[3].(string) + "\n" + strings.Repeat(".", len(old)-len("..")-1)
|
||||
})
|
||||
}
|
||||
withPayload := func(mk func(e *MutationEnv) (*MutationInput, error), edit func(p []byte) ([]byte, error)) func(e *MutationEnv) (*MutationInput, error) {
|
||||
return func(e *MutationEnv) (*MutationInput, error) {
|
||||
in, err := mk(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts, err := Split(in.DKC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload, err := edit(bytes.Clone(parts.Payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
in.DKC = Join(parts.Prelude, parts.Header, parts.Sealed, payload)
|
||||
return in, nil
|
||||
}
|
||||
}
|
||||
// chunk1 is the offset in PAYLOAD_AGE of its second STREAM chunk.
|
||||
chunk1 := func(p []byte) (int, error) {
|
||||
h, err := HeaderLen(p)
|
||||
return h + streamNonceSize + 64<<10 + 16, err
|
||||
}
|
||||
sigAlg1 := mustMarshal(map[uint64]any{0: uint64(1), 1: fill(0x11, 32), 2: fill(0x22, 64)})
|
||||
sealType1 := mustMarshal(map[uint64]any{0: uint64(1), 1: fill(0x33, 32)})
|
||||
named := func(m Mutation, name string) Mutation { m.Name = name; return m }
|
||||
|
||||
withIdentity := func(f *LoadedFixture, in *MutationInput) (*MutationInput, error) { return f.withIdentity(in) }
|
||||
relabelTK := func(e *MutationEnv) (*MutationInput, error) {
|
||||
return withIdentity(e.TimeAndKey3, relabeled(e.TimeAndKey3, 2))
|
||||
}
|
||||
|
||||
return []Mutation{
|
||||
// Format 3 exists since v0.10: VERSION 2 on a format 3 capsule fails
|
||||
// at step 14, where the schema version of the control is 3.
|
||||
{Name: "format 3 time_only relabeled format 2", Spec: true, Want: datekeys.ErrUnsupportedVersion, Step: 14, Network: true,
|
||||
Make: func(e *MutationEnv) (*MutationInput, error) { return ok(relabeled(e.TimeOnly3, 2)) }},
|
||||
|
||||
// 17.2: the frame of BODY and the area.
|
||||
spec("AREA_LEN 0", integ, frame(0, 0)),
|
||||
spec("AREA_LEN 511", integ, frame(0, 511)),
|
||||
spec("AREA_LEN 513", integ, frame(0, 513)),
|
||||
spec("AREA_LEN 66048", integ, frame(0, 66048)),
|
||||
spec("SECURITY_LEN 0", integ, frame(4, 0)),
|
||||
spec("SECURITY_LEN 513, larger than AREA_LEN", integ, frame(4, 513)),
|
||||
spec("HEAD_LEN 0", integ, frame(8, 0)),
|
||||
spec("HEAD_LEN 2^24 + 1", integ, frame(8, 1<<24+1)),
|
||||
spec("12 + AREA_LEN + HEAD_LEN = L + 1", integ, func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte {
|
||||
l := uint32(capsule.BodyFrameSize + len(b.area) + len(b.head) + len(b.content))
|
||||
b.frame.HeadLen = l - capsule.BodyFrameSize - b.frame.AreaLen + 1
|
||||
return b.bytes()
|
||||
}, nil)
|
||||
}),
|
||||
spec("L < 12: 11", integ, func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte { return b.bytes()[:11] }, nil)
|
||||
}),
|
||||
spec("a byte of the area not zero", integ, func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte {
|
||||
b.area = bytes.Clone(b.area)
|
||||
b.area[len(b.area)-1] = 1
|
||||
return b.bytes()
|
||||
}, nil)
|
||||
}),
|
||||
|
||||
// 17.4: the head, layers 2 and 3.
|
||||
spec("head of version 2", datekeys.ErrUnsupportedVersion, head(func(h map[uint64]any) { h[1] = uint64(2) })),
|
||||
spec("head of another type tag", nc, head(func(h map[uint64]any) { h[0] = "datekeys-heaD" })),
|
||||
spec("head with a byte more within HEAD_LEN", nc, headBytes(func(h []byte) []byte { return append(h, 0) })),
|
||||
spec("paths b and a, in that order", nc, two("b", "a")),
|
||||
spec("paths b/.. and a, in that order", nc, two("b/..", "a")),
|
||||
spec("path of 1025 bytes", nc, path(strings.Repeat("a/", 512)+"a")),
|
||||
|
||||
// 17.4: layer 4, in key order.
|
||||
spec("path ..", hi, path("..")),
|
||||
spec("path /a", hi, path("/a")),
|
||||
spec("paths A.txt and a.txt", hi, two("A.txt", "a.txt")),
|
||||
spec("paths ab and a, U+200C, b", hi, two("ab", "a\u200cb")),
|
||||
spec("path CON.txt", hi, path("CON.txt")),
|
||||
spec("path CON.txt in full-width forms", hi, path("\uff23\uff2f\uff2e.txt")),
|
||||
spec("path ABCDEF~1", hi, path("ABCDEF~1")),
|
||||
spec("path with U+202E", hi, path("a\u202eb.txt")),
|
||||
spec("path .datekeys-x", hi, path(".datekeys-x")),
|
||||
spec("comment with U+202E", hi, head(func(h map[uint64]any) { h[3] = "a\u202eb" })),
|
||||
spec("path a.", hi, path("a.")),
|
||||
spec("paths A and a/b", hi, two("A", "a/b")),
|
||||
spec("65536 implicit folders", hi, head(func(h map[uint64]any) { h[5] = implicitFolders(65536) })),
|
||||
spec("declared author with LF", hi, head(func(h map[uint64]any) { h[4] = "Ana\u000aLópez" })),
|
||||
spec("start of the first entry not 0", hi, head(func(h map[uint64]any) { file0(h, 2, uint64(1)); file0(h, 3, uint64(23)) })),
|
||||
spec("end - start not size", hi, head(func(h map[uint64]any) { file0(h, 3, uint64(21)) })),
|
||||
spec("path a followed by VS16", hi, path("a\ufe0f")),
|
||||
spec("path with two ZWJ in a row", hi, path("a\u200d\u200db")),
|
||||
spec("comment with the tag U+E0041", hi, head(func(h map[uint64]any) { h[3] = "Hola\U000e0041" })),
|
||||
spec("comment with the variation selector VS17", hi, head(func(h map[uint64]any) { h[3] = "\U0001f600\U000e0100" })),
|
||||
spec("start of an entry not the end of the one before", hi, head(func(h map[uint64]any) {
|
||||
h[5] = []any{entry("a", 11, 0, 11), entry("b", 11, 12, 23)}
|
||||
})),
|
||||
|
||||
// 17.5, 17.7 and 17.8.
|
||||
spec("end of the last file not C", integ, head(func(h map[uint64]any) { file0(h, 1, uint64(21)); file0(h, 3, uint64(21)) })),
|
||||
spec("a byte of a file changed", integ, func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte {
|
||||
b.content = bytes.Clone(b.content)
|
||||
b.content[0] ^= 1
|
||||
return b.bytes()
|
||||
}, nil)
|
||||
}),
|
||||
|
||||
// Precedence: the head fails first; what follows decides only when it
|
||||
// is a failure of age or of the length.
|
||||
spec("path .. and a padding byte not zero", hi, func(e *MutationEnv) (*MutationInput, error) {
|
||||
return e.body3Edit(e.TimeOnly3, func(b body3) []byte {
|
||||
m, err := cbortest.UnmarshalMap(b.head)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
file0(m, 0, "..")
|
||||
return b.withHeadBytes(mustMarshal(m))
|
||||
}, lastPadding)
|
||||
}),
|
||||
spec("path .. and the next STREAM chunk corrupt", integ, withPayload(treeDotDot, func(p []byte) ([]byte, error) {
|
||||
at, err := chunk1(p)
|
||||
if err != nil || at+100 >= len(p) {
|
||||
return nil, errors.New("testkit: format3_tree has no second chunk")
|
||||
}
|
||||
p[at+100] ^= 1
|
||||
return p, nil
|
||||
})),
|
||||
spec("path .. and a cut right after its chunk", integ, withPayload(treeDotDot, func(p []byte) ([]byte, error) {
|
||||
at, err := chunk1(p)
|
||||
if err != nil || at >= len(p) {
|
||||
return nil, errors.New("testkit: format3_tree has no second chunk")
|
||||
}
|
||||
return p[:at], nil
|
||||
})),
|
||||
|
||||
// Security never decides the opening: these open, without a code,
|
||||
// with their verdicts (spec §29.3, §29.7).
|
||||
named(security(securityV2(), capsule.Verdicts{Signature: capsule.VerdictUnreadable, Seal: capsule.VerdictUnreadable}), "security of version 2 opens with the verdict X"),
|
||||
named(security(mustSecurity(sigAlg1, nil), capsule.Verdicts{Signature: capsule.VerdictSignatureUnchecked, Seal: capsule.VerdictNoSeal}), "a signature of alg 1 opens with the verdict F1"),
|
||||
named(security(mustSecurity(nil, sealType1), capsule.Verdicts{Signature: capsule.VerdictNoSignature, Seal: capsule.VerdictSealUnsupported}), "a seal of seal_type 1 opens with the verdict S1"),
|
||||
|
||||
// Further cases: the other relabelings of a format 3 capsule.
|
||||
{Name: "format 3 time_only relabeled format 1", Want: datekeys.ErrUnsupportedVersion, Step: 14, Network: true,
|
||||
Make: func(e *MutationEnv) (*MutationInput, error) { return ok(relabeled(e.TimeOnly3, 1)) }},
|
||||
{Name: "format 3 time_and_key relabeled format 2, with the identity", Want: datekeys.ErrUnsupportedVersion, Step: 14, Network: true, Make: relabelTK},
|
||||
{Name: "format 3 time_and_key relabeled format 2, with the .dkk", Want: datekeys.ErrAccessInvalid, Step: 9,
|
||||
Make: func(e *MutationEnv) (*MutationInput, error) {
|
||||
in, err := relabelTK(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
in.DKK, in.Identities = e.TimeAndKey3.DKK, nil
|
||||
return in, nil
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// mustSecurity is SECURITY_CBOR with the given contents of keys 2 and 3.
|
||||
func mustSecurity(signature, seal []byte) []byte {
|
||||
b, err := capsule.EncodeSecurityWith(signature, seal)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue