Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
//go:build ignore
// Prints src/lib/dkc/testing/capsule-vectors.json: the verdicts of the Go
// reference on what scripts/capsule-ts-samples.mjs wrote with the writer of
// this repository (plan of phase 3, decision 11 and section 8, point 9):
//
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// - samples: each .dkc, of format 2 or 3, is inspected with capsule.Inspect
// and opened with capsule.Open and the published signature of its round,
// with each credential alone and with all of them, and the verdict, the
// format, L, the padding rule and P are recorded; in format 2 the SHA-256
// of the content, and in format 3 the files that a Sink receives, with
// their SHA-256, the head encoded again with capsule.EncodeHead and the
// verdicts of the security area. Its PUBLIC_HEADER, its CONTROL_CBOR,
// opened layer by layer with the identities of agewrap, and its .dkk are
// encoded again by the reference and compared with the bytes written,
// and the number of stanzas of its INNER_ACCESS_AGE is recorded.
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// - mixes: the code and the step at which capsule.Open fails.
// - encoders: capsule.EncodeHeader, capsule.EncodeControl (format 2) and
// AccessKey.MarshalBody on every input, compared with the bytes of the
// TypeScript library.
// - recipients: age.ParseX25519Recipient, then agewrap.CheckX25519Recipient,
// on every string.
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// - errors: the text of capsule.Encrypt for each invalid option, as a
// generator of test vectors, the only writer of format 2 (spec §62.1
// rule 1).
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
//
// Run it from a scratch module that requires the reference implementation
// (replace g.activething.com/go/DateKeys => ../datekeys-go, GOFLAGS=-mod=mod
// and the go directive of the reference, so that its toolchain is used),
// passing the output of capsule-ts-samples.mjs:
//
// go run capsule-go-verdicts.go ts-samples.json > capsule-vectors.json
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"maps"
"os"
"runtime/debug"
"slices"
"strings"
"time"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/codec/bech32"
"g.activething.com/go/DateKeys/datekey"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
// The published Quicknet signatures of the rounds of the samples, as in the
// fixtures; provider.Verify checks them in capsule.Open.
var published = map [ uint64 ] string {
1000 : "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39" ,
1001 : "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41" ,
2000 : "b6cb8f482a0b15d45936a4c4ea08e98a087e71787caee3f4d07a8a9843b1bc5423c6b3c22f446488b3137eaca799c77e" ,
}
type extJSON struct {
ID string ` json:"id" `
Version uint64 ` json:"version" `
Data * string ` json:"data,omitempty" `
}
type sampleIn struct {
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
Name string ` json:"name" `
Round uint64 ` json:"round" `
Policy string ` json:"policy" `
Format int ` json:"format" `
Length uint64 ` json:"length" `
Padding int ` json:"padding" `
PaddedLength uint64 ` json:"padded_length" `
// Format 2: the SHA-256 of the content. Format 3: the head written and
// its files.
ContentSHA256 string ` json:"content_sha256,omitempty" `
HeadCBOR string ` json:"head_cbor,omitempty" `
Files [ ] fileJSON ` json:"files,omitempty" `
Identities [ ] string ` json:"identities" `
DKK string ` json:"dkk,omitempty" `
Known [ ] extJSON ` json:"known" `
DKC string ` json:"dkc" `
}
// A file of a format 3 capsule: its path, size, SHA-256 and mtime.
type fileJSON struct {
Path string ` json:"path" `
Size uint64 ` json:"size" `
SHA256 string ` json:"sha256" `
MTime * uint64 ` json:"mtime,omitempty" `
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
}
type encoderIn struct {
Header struct {
CapsuleID string ` json:"capsule_id" `
Round uint64 ` json:"round" `
Policy int ` json:"policy" `
Critical [ ] extJSON ` json:"critical" `
Noncritical [ ] extJSON ` json:"noncritical" `
} ` json:"header" `
Control struct {
HeaderBinding string ` json:"header_binding" `
PayloadIdentity string ` json:"payload_identity" `
PayloadLength uint64 ` json:"payload_length" `
Padding int ` json:"padding" `
Critical [ ] extJSON ` json:"critical" `
Noncritical [ ] extJSON ` json:"noncritical" `
} ` json:"control" `
DKK struct {
CredentialID string ` json:"credential_id" `
CapsuleID string ` json:"capsule_id" `
Material string ` json:"material" `
CapsuleDigest * string ` json:"capsule_digest,omitempty" `
Critical [ ] extJSON ` json:"critical" `
Noncritical [ ] extJSON ` json:"noncritical" `
} ` json:"dkk" `
HeaderCBOR string ` json:"header_cbor" `
ControlCBOR string ` json:"control_cbor" `
DKKBody string ` json:"dkk_body" `
}
type errorCase struct {
Name string ` json:"name" `
Policy int ` json:"policy" `
Recipients [ ] string ` json:"recipients,omitempty" `
Portable bool ` json:"portable,omitempty" `
UnlockAt string ` json:"unlock_at" `
Now string ` json:"now" `
Length int64 ` json:"length" `
Padding int ` json:"padding,omitempty" `
Critical [ ] extJSON ` json:"critical,omitempty" `
Noncritical [ ] extJSON ` json:"noncritical,omitempty" `
ControlCritical [ ] extJSON ` json:"control_critical,omitempty" `
ControlNoncritical [ ] extJSON ` json:"control_noncritical,omitempty" `
ChainHashFlip bool ` json:"chain_hash_flip,omitempty" `
TS string ` json:"ts" `
Go string ` json:"go" `
}
type input struct {
Generator string ` json:"generator" `
Samples [ ] sampleIn ` json:"samples" `
Mixes [ ] struct {
Name string ` json:"name" `
Round uint64 ` json:"round" `
DKC string ` json:"dkc" `
} ` json:"mixes" `
Encoders struct {
Seed int ` json:"seed" `
Count int ` json:"count" `
SHA256 string ` json:"sha256" `
Cases [ ] encoderIn ` json:"cases" `
} ` json:"encoders" `
Recipients [ ] string ` json:"recipients" `
Errors [ ] errorCase ` json:"errors" `
}
// The verdict of one opening: "ok" with what was delivered, or the code and
// the step of the failure.
type openVerdict struct {
Credentials string ` json:"credentials" `
Result string ` json:"result" `
Step int ` json:"step,omitempty" `
Opened * openedOut ` json:"opened,omitempty" `
}
type openedOut struct {
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
ContentSHA256 string ` json:"content_sha256,omitempty" `
Format int ` json:"format" `
Length uint64 ` json:"length" `
Padding int ` json:"padding" `
PaddedLength uint64 ` json:"padded_length" `
Files [ ] fileJSON ` json:"files,omitempty" `
HeadCBOR string ` json:"head_cbor,omitempty" `
Verdicts [ ] string ` json:"verdicts,omitempty" `
}
// sink keeps the files of a format 3 capsule in memory.
type sink struct {
files [ ] * bytes . Buffer
}
type buffer struct { * bytes . Buffer }
func ( buffer ) Close ( ) error { return nil }
func ( s * sink ) Begin ( * capsule . Head ) error { return nil }
func ( s * sink ) Create ( int ) ( io . WriteCloser , error ) {
b := & bytes . Buffer { }
s . files = append ( s . files , b )
return buffer { b } , nil
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
}
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
func ( s * sink ) Commit ( ) error { return nil }
func ( s * sink ) Abort ( ) { s . files = nil }
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
type sampleOut struct {
sampleIn
Go struct {
Inspect string ` json:"inspect" `
Opens [ ] openVerdict ` json:"opens" `
InnerStanzas int ` json:"inner_stanzas" `
Reencoded bool ` json:"reencoded" `
} ` json:"go" `
}
type mixOut struct {
Name string ` json:"name" `
Round uint64 ` json:"round" `
DKC string ` json:"dkc" `
Go struct {
Code string ` json:"code" `
Step int ` json:"step" `
} ` json:"go" `
}
type recipientOut struct {
String string ` json:"string" `
Parse string ` json:"parse" `
Check string ` json:"check,omitempty" `
}
type output struct {
Description string ` json:"description" `
Generator string ` json:"generator" `
SamplesGenerator string ` json:"samples_generator" `
Libraries string ` json:"libraries" `
Releases [ ] releaseOut ` json:"releases" `
Samples [ ] sampleOut ` json:"samples" `
Mixes [ ] mixOut ` json:"mixes" `
Encoders encodersOut ` json:"encoders" `
Recipients [ ] recipientOut ` json:"recipients" `
Errors [ ] errorCase ` json:"errors" `
}
type releaseOut struct {
Round uint64 ` json:"round" `
Signature string ` json:"signature" `
}
type encodersOut struct {
Seed int ` json:"seed" `
Count int ` json:"count" `
SHA256 string ` json:"sha256" `
Equal int ` json:"equal" `
Differences [ ] string ` json:"differences" `
}
func must [ T any ] ( v T , err error ) T {
if err != nil {
panic ( err )
}
return v
}
func unhex ( s string ) [ ] byte { return must ( hex . DecodeString ( s ) ) }
func exts ( list [ ] extJSON ) [ ] extension . Extension {
var out [ ] extension . Extension
for _ , e := range list {
x := extension . Extension { ID : e . ID , Version : e . Version }
if e . Data != nil {
x . Data = unhex ( * e . Data )
if x . Data == nil {
x . Data = [ ] byte { }
}
}
out = append ( out , x )
}
return out
}
func source ( round uint64 ) provider . ReleaseSource {
return provider . ReleaseSourceFunc ( func ( _ context . Context , _ * profile . Profile , c provider . Condition ) ( provider . Release , error ) {
return provider . Release { Round : c . Round , Signature : unhex ( published [ c . Round ] ) } , nil
} )
}
func opened ( dkc [ ] byte , round uint64 , reg profile . Registry , ex extension . Registry , ids [ ] age . Identity , dkk [ ] byte ) openVerdict {
var out bytes . Buffer
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
files := & sink { }
o := capsule . OpenOptions { Registry : reg , Extensions : ex , Source : source ( round ) , Identities : ids , Sink : files , Now : func ( ) time . Time {
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
return time . Unix ( 1692803367 + int64 ( round - 1 ) * 3 , 0 )
} }
if dkk != nil {
o . AccessKeyFile = bytes . NewReader ( dkk )
}
res , err := capsule . Open ( context . Background ( ) , & out , bytes . NewReader ( dkc ) , o )
v := openVerdict { Result : "ok" }
if err != nil {
v . Result = datekeys . Code ( err )
if v . Result == "" {
v . Result = "error: " + err . Error ( )
}
if res != nil && res . Inspection != nil && len ( res . Inspection . Checks ) > 0 {
v . Step = res . Inspection . Checks [ len ( res . Inspection . Checks ) - 1 ] . Step
}
return v
}
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
v . Opened = & openedOut { Format : int ( res . Format ) , Length : res . PayloadLength , Padding : int ( res . Padding ) , PaddedLength : res . PaddedLength }
if res . Format != capsule . Format3 {
sum := sha256 . Sum256 ( out . Bytes ( ) )
v . Opened . ContentSHA256 = hex . EncodeToString ( sum [ : ] )
return v
}
for i , f := range res . Head . Files {
sum := sha256 . Sum256 ( files . files [ i ] . Bytes ( ) )
fj := fileJSON { Path : f . Path , Size : f . Size , SHA256 : hex . EncodeToString ( sum [ : ] ) }
if f . HasMTime {
mtime := f . MTime
fj . MTime = & mtime
}
v . Opened . Files = append ( v . Opened . Files , fj )
}
v . Opened . HeadCBOR = hex . EncodeToString ( must ( capsule . EncodeHead ( res . Head ) ) )
v . Opened . Verdicts = [ ] string { string ( res . Verdicts . Signature ) , string ( res . Verdicts . Seal ) }
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
return v
}
func main ( ) {
var in input
must ( 0 , json . Unmarshal ( must ( os . ReadFile ( os . Args [ 1 ] ) ) , & in ) )
reg := must ( profile . Default ( ) )
p := profile . Quicknet ( )
out := output {
Description : "Verdicts of the Go reference on capsules, mixes, encodings, recipients and invalid options of the TypeScript writer (plan of phase 3, section 8, point 9); see the header of scripts/capsule-go-verdicts.go." ,
Generator : "scripts/capsule-go-verdicts.go" ,
SamplesGenerator : in . Generator ,
Libraries : libraries ( ) ,
}
for _ , r := range slices . Sorted ( maps . Keys ( published ) ) {
out . Releases = append ( out . Releases , releaseOut { r , published [ r ] } )
}
for _ , s := range in . Samples {
dkc := unhex ( s . DKC )
known := extension . Set { }
for _ , k := range s . Known {
known [ k . ID ] = append ( known [ k . ID ] , k . Version )
}
so := sampleOut { sampleIn : s }
if _ , err := capsule . Inspect ( bytes . NewReader ( dkc ) , capsule . InspectOptions { Registry : reg , Extensions : known } ) ; err != nil {
so . Go . Inspect = datekeys . Code ( err )
} else {
so . Go . Inspect = "ok"
}
var ids [ ] age . Identity
for i , raw := range s . Identities {
id := must ( agewrap . X25519IdentityFromRaw ( unhex ( raw ) ) )
ids = append ( ids , id )
v := opened ( dkc , s . Round , reg , known , [ ] age . Identity { id } , nil )
v . Credentials = fmt . Sprintf ( "identity %d" , i )
so . Go . Opens = append ( so . Go . Opens , v )
}
var dkk [ ] byte
if s . DKK != "" {
dkk = unhex ( s . DKK )
v := opened ( dkc , s . Round , reg , known , nil , dkk )
v . Credentials = ".dkk"
so . Go . Opens = append ( so . Go . Opens , v )
}
v := opened ( dkc , s . Round , reg , known , ids , dkk )
v . Credentials = "all"
if ids == nil && dkk == nil {
v . Credentials = "none"
}
so . Go . Opens = append ( so . Go . Opens , v )
so . Go . InnerStanzas , so . Go . Reencoded = layers ( dkc , s , p , dkk )
out . Samples = append ( out . Samples , so )
}
for _ , m := range in . Mixes {
v := opened ( unhex ( m . DKC ) , m . Round , reg , extension . Set { } , nil , nil )
mo := mixOut { Name : m . Name , Round : m . Round , DKC : m . DKC }
mo . Go . Code , mo . Go . Step = v . Result , v . Step
out . Mixes = append ( out . Mixes , mo )
}
out . Encoders = encodersOut { Seed : in . Encoders . Seed , Count : in . Encoders . Count , SHA256 : in . Encoders . SHA256 , Differences : [ ] string { } }
for i , c := range in . Encoders . Cases {
if d := encoders ( c ) ; d != "" {
out . Encoders . Differences = append ( out . Encoders . Differences , fmt . Sprintf ( "case %d: %s" , i , d ) )
} else {
out . Encoders . Equal ++
}
}
for _ , s := range in . Recipients {
r := recipientOut { String : s , Parse : "ok" }
x , err := age . ParseX25519Recipient ( s )
if err != nil {
r . Parse = err . Error ( )
} else if err := agewrap . CheckX25519Recipient ( x ) ; err != nil {
r . Check = err . Error ( )
} else {
r . Check = "ok"
}
out . Recipients = append ( out . Recipients , r )
}
for _ , c := range in . Errors {
c . Go = encryptError ( c )
out . Errors = append ( out . Errors , c )
}
enc := json . NewEncoder ( os . Stdout )
enc . SetIndent ( "" , " " )
must ( 0 , enc . Encode ( out ) )
}
// layers opens SEALED_CONTROL of a sample with the published release and, in
// time_and_key, with its first credential, and encodes PUBLIC_HEADER,
// CONTROL_CBOR and the .dkk again. It returns the number of stanzas of
// INNER_ACCESS_AGE and whether every encoding equals the bytes written.
func layers ( dkc [ ] byte , s sampleIn , p * profile . Profile , dkk [ ] byte ) ( int , bool ) {
pre := must ( capsule . ParsePrelude ( dkc ) )
header := dkc [ capsule . PreludeSize : capsule . PreludeSize + int ( pre . PublicHeaderLen ) ]
sealed := dkc [ capsule . PreludeSize + int ( pre . PublicHeaderLen ) : capsule . PreludeSize + int ( pre . PublicHeaderLen ) + int ( pre . SealedControlLen ) ]
same := true
h := must ( capsule . DecodeHeader ( header ) )
same = same && bytes . Equal ( must ( capsule . EncodeHeader ( h ) ) , header )
tid := must ( agewrap . NewTimeIdentity ( p , s . Round , provider . Release { Round : s . Round , Signature : unhex ( published [ s . Round ] ) } ) )
inner := must ( io . ReadAll ( must ( age . Decrypt ( bytes . NewReader ( sealed ) , tid ) ) ) )
control := inner
stanzas := 0
if s . Policy == "time_and_key" {
st := must ( agewrap . Stanzas ( bytes . NewReader ( inner ) ) )
stanzas = len ( st )
var id age . Identity
if len ( s . Identities ) > 0 {
id = must ( agewrap . X25519IdentityFromRaw ( unhex ( s . Identities [ 0 ] ) ) )
} else {
k := must ( accesskey . Decode ( bytes . NewReader ( dkk ) ) )
id = must ( agewrap . X25519IdentityFromRaw ( k . Material ) )
}
aid := must ( agewrap . NewAccessIdentity ( agewrap . AccessSlots , id ) )
control = must ( io . ReadAll ( must ( age . Decrypt ( bytes . NewReader ( inner ) , aid ) ) ) )
}
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
c := must ( capsule . DecodeControl ( control , pre . Format ) )
same = same && bytes . Equal ( must ( capsule . EncodeControl ( c , pre . Format ) ) , control )
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
if dkk != nil {
k := must ( accesskey . Decode ( bytes . NewReader ( dkk ) ) )
var b bytes . Buffer
must ( 0 , accesskey . Encode ( & b , k ) )
same = same && bytes . Equal ( b . Bytes ( ) , dkk )
}
return stanzas , same
}
// encoders encodes one input with the reference and names the first encoding
// that differs from the bytes of the TypeScript library, or returns "".
func encoders ( c encoderIn ) string {
var h capsule . Header
copy ( h . CapsuleID [ : ] , unhex ( c . Header . CapsuleID ) )
h . DateKey = datekey . DateKey { ProfileID : "datekeys:quicknet:v1" , Round : c . Header . Round }
h . Policy = capsule . Policy ( c . Header . Policy )
h . Critical , h . Noncritical = exts ( c . Header . Critical ) , exts ( c . Header . Noncritical )
hb , err := capsule . EncodeHeader ( & h )
if err != nil || hex . EncodeToString ( hb ) != c . HeaderCBOR {
return fmt . Sprintf ( "PUBLIC_HEADER (%v)" , err )
}
var ctrl capsule . Control
copy ( ctrl . HeaderBinding [ : ] , unhex ( c . Control . HeaderBinding ) )
copy ( ctrl . PayloadIdentity [ : ] , unhex ( c . Control . PayloadIdentity ) )
ctrl . PayloadLength , ctrl . Padding = c . Control . PayloadLength , capsule . Padding ( c . Control . Padding )
ctrl . Critical , ctrl . Noncritical = exts ( c . Control . Critical ) , exts ( c . Control . Noncritical )
cb , err := capsule . EncodeControl ( & ctrl , capsule . Format2 )
if err != nil || hex . EncodeToString ( cb ) != c . ControlCBOR {
return fmt . Sprintf ( "CONTROL_CBOR (%v)" , err )
}
k := accesskey . AccessKey { Type : accesskey . TypeX25519 , Material : unhex ( c . DKK . Material ) }
copy ( k . CredentialID [ : ] , unhex ( c . DKK . CredentialID ) )
copy ( k . CapsuleID [ : ] , unhex ( c . DKK . CapsuleID ) )
if c . DKK . CapsuleDigest != nil {
k . Verification = & accesskey . Verification { CapsuleDigest : unhex ( * c . DKK . CapsuleDigest ) }
}
k . Critical , k . Noncritical = exts ( c . DKK . Critical ) , exts ( c . DKK . Noncritical )
kb , err := k . MarshalBody ( )
if err != nil || hex . EncodeToString ( kb ) != c . DKKBody {
return fmt . Sprintf ( ".dkk BODY_CBOR (%v)" , err )
}
return ""
}
// encryptError runs capsule.Encrypt with the options of a case and returns
// its error text, or "ok".
func encryptError ( c errorCase ) string {
p := profile . Quicknet ( )
if c . ChainHashFlip {
p . ChainHash [ 0 ] ^ = 1
}
var recipients [ ] age . Recipient
for _ , raw := range c . Recipients {
s := must ( bech32 . Encode ( "age" , unhex ( raw ) ) )
recipients = append ( recipients , must ( age . ParseX25519Recipient ( s ) ) )
}
opts := capsule . EncryptOptions {
Profile : p ,
UnlockAt : must ( time . Parse ( time . RFC3339Nano , c . UnlockAt ) ) ,
Policy : capsule . Policy ( c . Policy ) ,
Recipients : recipients ,
NewPortableKey : c . Portable ,
Length : c . Length ,
Padding : capsule . Padding ( c . Padding ) ,
Critical : exts ( c . Critical ) ,
Noncritical : exts ( c . Noncritical ) ,
ControlCritical : exts ( c . ControlCritical ) ,
ControlNoncritical : exts ( c . ControlNoncritical ) ,
Format 3, step 4: Go opens what encryptFiles writes
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
TestVectors : true ,
Phase 3, step 5: interoperability with Go at the level of the capsule
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
Now : func ( ) time . Time { return must ( time . Parse ( time . RFC3339Nano , c . Now ) ) } ,
}
src := bytes . NewReader ( make ( [ ] byte , min ( c . Length , 1 ) ) )
if _ , err := capsule . Encrypt ( io . Discard , src , opts ) ; err != nil {
return err . Error ( )
}
return "ok"
}
func libraries ( ) string {
info , ok := debug . ReadBuildInfo ( )
if ! ok {
return ""
}
var parts [ ] string
for _ , d := range info . Deps {
switch d . Path {
case "filippo.io/age" , "github.com/drand/drand/v2" , "github.com/drand/kyber" , "github.com/drand/kyber-bls12381" , "github.com/drand/tlock" :
parts = append ( parts , d . Path + " " + d . Version )
}
}
return strings . Join ( parts , ", " )
}