diff --git a/go/README.md b/go/README.md index 311c8dd..e6903ea 100644 --- a/go/README.md +++ b/go/README.md @@ -1 +1,19 @@ -PLACEHOLDER \ No newline at end of file +# mesh-sdk for Go + +`stdio.Serve("", tools)` speaks MCP over stdio to the node's tool runtime, as the module the runtime +names (novox/hq ADR 0188, ADR 0193). A `Tool` is a name, a description, an argument schema and a +function; calls run concurrently. `stdio.CallerOf(args)` inside a tool is who called it: the bus user the runtime took from the subject the call arrived on, named in the call's `_meta`, never read from the arguments — or `stdio.Unproven` when the runtime named none (novox/hq issue 365). A tool named `.` is the seat's implementation. +`stdio.Emit(type, body)` publishes an event as the module, `stdio.Subscribe(pattern, handler)` receives the module's events (taken when the handler returns nil, offered again when it returns an error — the one rule is in the repository README, "Taking an event") and `stdio.Ask(key, body)` calls a tool, and `stdio.State(name)` gets, puts, deletes, lists, watches and reads the kept history of the module's declared state, and `stdio.Log(name)` appends to, reads, and measures a log of operations the module declared — all through the runtime (novox/hq ADR 0193, 0198, 0201, 0297, 0307). Standard library only. + +## Installing + +``` +GOPRIVATE=git.novox.be go get git.novox.be/novox/mesh-sdk/go@latest +``` + +Releases are tagged `go/vX.Y.Z` in this repository, the subdirectory form Go's module system expects, +at the same version as the TypeScript package. + +What it holds, under ADR 0039's test: the stdio loop and the tool type, and `cli`, the local protocol +`mesh-cli` speaks to the node-engine on its machine (novox/hq ADR 0272): the socket's path and one request and one +answer; and `seal`, the mesh's sealed box (novox/hq ADR 0206: X25519, HKDF-SHA256, AES-256-GCM, `{v, eph, iv, tag, ct}`), byte for byte the box the licence manager made before it lived here and the TypeScript `@novox/mesh-sdk/seal` makes. Nothing else — no bus, no client, nothing of any module's. `go build ./example` and pipe a `tools/list` request into it. diff --git a/go/seal/seal.go b/go/seal/seal.go new file mode 100644 index 0000000..0aeb8ca --- /dev/null +++ b/go/seal/seal.go @@ -0,0 +1,203 @@ +// Package seal is the mesh's sealed box (novox/hq ADR 0183, ADR 0206): a value sealed to one recipient's +// public key, which only that recipient's private key opens. X25519 for the agreement, HKDF-SHA256 for the +// key, AES-256-GCM for the box — the box the licence manager made and opened before it lived here, byte +// for byte, and the one the SDK's TypeScript `seal` makes and opens. +// +// A box is `{ v: 1, eph, iv, tag, ct }`, every field standard base64; `eph` is the one-time public key as +// SPKI DER. The key is derived with the salt eph-DER ‖ the recipient's raw public key and the info +// "novox-mesh sealed box v1", so a box cannot be re-addressed to another key. +// +// Keys travel as PEM: the public key as SPKI ("PUBLIC KEY"), the private key as PKCS #8 ("PRIVATE KEY"). +// Standard library only. +package seal + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdh" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" +) + +// Box is a value sealed to one recipient, as it travels in JSON. +type Box struct { + V int `json:"v"` + Eph string `json:"eph"` + IV string `json:"iv"` + Tag string `json:"tag"` + Ct string `json:"ct"` +} + +// KeyPair is a recipient's keypair as the two PEM strings it is kept and sent as. +type KeyPair struct { + PublicKey string `json:"publicKey"` + PrivateKey string `json:"privateKey"` +} + +// Version is the only box version Open opens. +const Version = 1 + +const info = "novox-mesh sealed box v1" + +// GenerateKeyPair makes a fresh X25519 keypair, PEM-encoded. +func GenerateKeyPair() (KeyPair, error) { + priv, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return KeyPair{}, err + } + pubDER, err := x509.MarshalPKIXPublicKey(priv.PublicKey()) + if err != nil { + return KeyPair{}, err + } + privDER, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return KeyPair{}, err + } + return KeyPair{ + PublicKey: string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDER})), + PrivateKey: string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privDER})), + }, nil +} + +// Seal seals plaintext to the recipient's PEM public key, with a fresh one-time key and iv. +func Seal(plaintext, recipientPEM string) (Box, error) { + eph, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return Box{}, err + } + iv := make([]byte, 12) + if _, err := rand.Read(iv); err != nil { + return Box{}, err + } + return sealWith(plaintext, recipientPEM, eph, iv) +} + +// sealWith is Seal with its two sources of randomness given, for the byte-for-byte tests. +func sealWith(plaintext, recipientPEM string, eph *ecdh.PrivateKey, iv []byte) (Box, error) { + recipient, err := publicFromPEM(recipientPEM) + if err != nil { + return Box{}, err + } + secret, err := eph.ECDH(recipient) + if err != nil { + return Box{}, err + } + ephDER, err := x509.MarshalPKIXPublicKey(eph.PublicKey()) + if err != nil { + return Box{}, err + } + gcm, err := newGCM(boxKey(secret, ephDER, recipient.Bytes())) + if err != nil { + return Box{}, err + } + out := gcm.Seal(nil, iv, []byte(plaintext), nil) + ct, tag := out[:len(out)-gcm.Overhead()], out[len(out)-gcm.Overhead():] + b64 := base64.StdEncoding.EncodeToString + return Box{V: Version, Eph: b64(ephDER), IV: b64(iv), Tag: b64(tag), Ct: b64(ct)}, nil +} + +// Open opens a box with the recipient's PEM private key. It fails for a box sealed to another key, one +// changed in any field, or one of another version. +func Open(box Box, privatePEM string) (string, error) { + if box.V != Version { + return "", fmt.Errorf("a sealed box of version %d, not %d", box.V, Version) + } + priv, err := privateFromPEM(privatePEM) + if err != nil { + return "", err + } + d := base64.StdEncoding.DecodeString + ephDER, err := d(box.Eph) + if err != nil { + return "", fmt.Errorf("the box's eph: %w", err) + } + ephKey, err := x509.ParsePKIXPublicKey(ephDER) + if err != nil { + return "", fmt.Errorf("the box's eph: %w", err) + } + eph, ok := ephKey.(*ecdh.PublicKey) + if !ok || eph.Curve() != ecdh.X25519() { + return "", errors.New("the box's eph is not an X25519 key") + } + secret, err := priv.ECDH(eph) + if err != nil { + return "", err + } + iv, err1 := d(box.IV) + tag, err2 := d(box.Tag) + ct, err3 := d(box.Ct) + if err := errors.Join(err1, err2, err3); err != nil { + return "", fmt.Errorf("the box's fields: %w", err) + } + gcm, err := newGCM(boxKey(secret, ephDER, priv.PublicKey().Bytes())) + if err != nil { + return "", err + } + if len(iv) != gcm.NonceSize() || len(tag) != gcm.Overhead() { + return "", errors.New("the box does not open with this key") + } + plain, err := gcm.Open(nil, iv, append(append([]byte{}, ct...), tag...), nil) + if err != nil { + return "", errors.New("the box does not open with this key") + } + return string(plain), nil +} + +func publicFromPEM(p string) (*ecdh.PublicKey, error) { + block, _ := pem.Decode([]byte(p)) + if block == nil { + return nil, errors.New("not a PEM public key") + } + k, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + pub, ok := k.(*ecdh.PublicKey) + if !ok || pub.Curve() != ecdh.X25519() { + return nil, errors.New("not an X25519 public key") + } + return pub, nil +} + +func privateFromPEM(p string) (*ecdh.PrivateKey, error) { + block, _ := pem.Decode([]byte(p)) + if block == nil { + return nil, errors.New("not a PEM private key") + } + k, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + priv, ok := k.(*ecdh.PrivateKey) + if !ok || priv.Curve() != ecdh.X25519() { + return nil, errors.New("not an X25519 private key") + } + return priv, nil +} + +// boxKey is HKDF-SHA256 (RFC 5869) for one 32-byte block: written out because crypto/hkdf needs Go 1.24 +// and this module promises 1.22; the vectors prove it equals the licence manager's hkdf.Key. +func boxKey(secret, ephDER, recipientRaw []byte) []byte { + salt := append(append([]byte{}, ephDER...), recipientRaw...) + extract := hmac.New(sha256.New, salt) + extract.Write(secret) + prk := extract.Sum(nil) + expand := hmac.New(sha256.New, prk) + expand.Write([]byte(info)) + expand.Write([]byte{1}) + return expand.Sum(nil) +} + +func newGCM(key []byte) (cipher.AEAD, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewGCM(block) +} diff --git a/go/seal/seal_test.go b/go/seal/seal_test.go new file mode 100644 index 0000000..2a2786e --- /dev/null +++ b/go/seal/seal_test.go @@ -0,0 +1,156 @@ +package seal + +import ( + "crypto/ecdh" + "encoding/base64" + "encoding/json" + "os" + "testing" +) + +type fixture struct { + Recipient KeyPair `json:"recipient"` + Sealed []struct { + Plaintext string `json:"plaintext"` + Box Box `json:"box"` + } `json:"sealed"` + Vectors []struct { + Plaintext string `json:"plaintext"` + EphPrivate string `json:"ephPrivate"` + IV string `json:"iv"` + Box Box `json:"box"` + } `json:"vectors"` +} + +func readFixture(t *testing.T) fixture { + t.Helper() + raw, err := os.ReadFile("testdata/sealed-by-licence-manager.json") + if err != nil { + t.Fatal(err) + } + var f fixture + if err := json.Unmarshal(raw, &f); err != nil { + t.Fatal(err) + } + if len(f.Sealed) == 0 || len(f.Vectors) == 0 { + t.Fatal("the fixture holds no boxes") + } + return f +} + +// Every box the licence manager's own Seal made opens here to its plaintext. +func TestABoxTheLicenceManagerSealedOpens(t *testing.T) { + f := readFixture(t) + for _, s := range f.Sealed { + got, err := Open(s.Box, f.Recipient.PrivateKey) + if err != nil || got != s.Plaintext { + t.Fatalf("opened %q, %v; want %q", got, err, s.Plaintext) + } + } +} + +// Given the same one-time key and iv, this package seals byte for byte what the licence manager sealed: +// the reverse direction, so whatever opens the licence manager's boxes opens these. +func TestSealIsByteForByteTheLicenceManagersBox(t *testing.T) { + f := readFixture(t) + for _, v := range f.Vectors { + d := base64.StdEncoding.DecodeString + ephRaw, err1 := d(v.EphPrivate) + iv, err2 := d(v.IV) + if err1 != nil || err2 != nil { + t.Fatal(err1, err2) + } + eph, err := ecdh.X25519().NewPrivateKey(ephRaw) + if err != nil { + t.Fatal(err) + } + got, err := sealWith(v.Plaintext, f.Recipient.PublicKey, eph, iv) + if err != nil { + t.Fatal(err) + } + if got != v.Box { + t.Fatalf("sealed %+v\nthe licence manager sealed %+v", got, v.Box) + } + if plain, err := Open(got, f.Recipient.PrivateKey); err != nil || plain != v.Plaintext { + t.Fatalf("reopened %q, %v", plain, err) + } + } +} + +// A box the agent module's TypeScript sealed (the fixture the licence manager keeps) opens here too. +func TestABoxSealedInTypeScriptOpens(t *testing.T) { + raw, err := os.ReadFile("testdata/sealed-by-typescript.json") + if err != nil { + t.Fatal(err) + } + var f struct { + PrivateKey string `json:"privateKey"` + Box Box `json:"box"` + Plaintext string `json:"plaintext"` + } + if err := json.Unmarshal(raw, &f); err != nil { + t.Fatal(err) + } + if got, err := Open(f.Box, f.PrivateKey); err != nil || got != f.Plaintext { + t.Fatalf("opened %q, %v", got, err) + } +} + +// What Seal makes opens with its recipient's key and no other, and a fresh box differs every time. +func TestABoxOpensOnlyForItsRecipient(t *testing.T) { + a, err := GenerateKeyPair() + if err != nil { + t.Fatal(err) + } + b, _ := GenerateKeyPair() + box, err := Seal("a token", a.PublicKey) + if err != nil { + t.Fatal(err) + } + if got, err := Open(box, a.PrivateKey); err != nil || got != "a token" { + t.Fatalf("opened %q, %v", got, err) + } + if _, err := Open(box, b.PrivateKey); err == nil { + t.Fatal("a box opened for another key") + } + again, _ := Seal("a token", a.PublicKey) + if again.Eph == box.Eph || again.IV == box.IV { + t.Fatal("two boxes share a one-time key or iv") + } +} + +// A box changed in any field, or of another version, does not open. +func TestATamperedBoxDoesNotOpen(t *testing.T) { + f := readFixture(t) + good := f.Sealed[1].Box + flip := func(s string) string { + b, _ := base64.StdEncoding.DecodeString(s) + b[len(b)-1] ^= 1 + return base64.StdEncoding.EncodeToString(b) + } + for name, box := range map[string]Box{ + "ct": {V: 1, Eph: good.Eph, IV: good.IV, Tag: good.Tag, Ct: flip(good.Ct)}, + "tag": {V: 1, Eph: good.Eph, IV: good.IV, Tag: flip(good.Tag), Ct: good.Ct}, + "iv": {V: 1, Eph: good.Eph, IV: flip(good.IV), Tag: good.Tag, Ct: good.Ct}, + "eph": {V: 1, Eph: flip(good.Eph), IV: good.IV, Tag: good.Tag, Ct: good.Ct}, + "version": {V: 2, Eph: good.Eph, IV: good.IV, Tag: good.Tag, Ct: good.Ct}, + } { + if _, err := Open(box, f.Recipient.PrivateKey); err == nil { + t.Fatalf("a box with a changed %s opened", name) + } + } +} + +// A key that is not X25519 PEM is refused, not misread. +func TestAKeyOfAnotherKindIsRefused(t *testing.T) { + if _, err := Seal("x", "not a key"); err == nil { + t.Fatal("sealed to text") + } + f := readFixture(t) + if _, err := Open(f.Sealed[0].Box, f.Recipient.PublicKey); err == nil { + t.Fatal("opened with a public key") + } + if _, err := Seal("x", f.Recipient.PrivateKey); err == nil { + t.Fatal("sealed to a private key") + } +} diff --git a/go/seal/testdata/sealed-by-licence-manager.json b/go/seal/testdata/sealed-by-licence-manager.json new file mode 100644 index 0000000..b85ea5a --- /dev/null +++ b/go/seal/testdata/sealed-by-licence-manager.json @@ -0,0 +1,121 @@ +{ + "about": "Made by the claude-licence-manager's seal.go (mesh-catalog feat/57-config-and-provisioning at 8aa37e8), unchanged but for its two sources of randomness, which the vectors fix. The recipient is a test key derived from a fixed label; it guards nothing.", + "recipient": { + "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VuAyEAkZXnghyfA1BR5rWtvBLmjdZMCbC99xxF3WKHe1E+3XM=\n-----END PUBLIC KEY-----\n", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VuBCIEIHjV954xXPa72FyGUw5PsWCbsHrliVp/7xoAgG9fGs7q\n-----END PRIVATE KEY-----\n" + }, + "sealed": [ + { + "plaintext": "", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAwN2gt32hv+9VyFh0oQ8oetPBHsZ9dt80DzwPwcB0bHg=", + "iv": "0xHCegxP6o48EGLq", + "tag": "VYQZm/lBZB0YMPUqYVPKaQ==", + "ct": "" + } + }, + { + "plaintext": "a grant sealed by the licence manager", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEA54v96LEb8SwT2uJZCa90/2jcLvEbK/BZpbMS6TxAAW0=", + "iv": "xm6Jg4oOnRpRpDGK", + "tag": "cmbOzIV63Nv0lPfpTgP2kg==", + "ct": "InWe8PZ8hkd7F9QaHujjhdiBECKzLAK22d2aIEXwBhtxIowc/Q==" + } + }, + { + "plaintext": "sk-ant-api03-test-0123456789abcdefghij\n", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAIXdum2FYLzvLDj/Of2253hH8Lu0szU4tLxJzfXTahDs=", + "iv": "KMPNa60C/Qjhtp0Z", + "tag": "PPa82ZdFyWz9kbLSSGkl3g==", + "ct": "Phu3ZsxIWGlBWbYOGzPopncEbc0VhTAloxzeHs4KDBMFZU9Pet2V" + } + }, + { + "plaintext": "ünïcødé ✓ and a NUL \u0000 inside", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAo1WaRyrAJFM6If/cpFgdpZjsC6XebuJtVSugbI+m8RU=", + "iv": "H9+WvM6ZYbCYJj6D", + "tag": "K5p986nSpeItYK1m4bcVPQ==", + "ct": "eyZs3uVeZguNbqSmrMc1rWgxhW3nP0GABH6Ek06/8UuBqw==" + } + }, + { + "plaintext": "a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; ", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAzjZdTMu08I0Z+FWXSr9f++EADfqg3qr2FVt+3Q/hxm0=", + "iv": "lTG2VwPA5SwHbZE/", + "tag": "Il6ICxoZgZr9dOgeIZOPrg==", + "ct": "ZWQ8m1nxrqq0oIGRoeNlUu9/JpTMrxlncTCL5cDqsJrx9NjS0nZX292JJ5p0+BYSVLUN+yGUuBOnH860ywvKK7XhPOc0Yo2nq7X2O3OYn9Za4y+HK6dVDM8zhsS8P3Di/MJCwS+AnremVCaj2k3xHL3oc331RBZZ5w2JfgSX3ouwl2+VbIPyJVSDsxnLLoHsiIR7nB0GNCe0fynhVkoIEwC8IXLnL6m3rjjN3YCQDmVP2UoftniLAypd5ioc9GCtpvUL6ktjs82JkvEtFbHtc+YzIjP6ZnjTj/bvbRHkIqN4MDiu+fvGOr0jtGWO24ecGC+6KtrKGOaEYHDqOLaK3c+UcrDsFYC+cuKCdCNhs5UNhYYylVXE648KFa488eFk" + } + } + ], + "vectors": [ + { + "plaintext": "", + "ephPrivate": "W8iT0SrRDnazANHYsXHC72sM1wzx+/Y8vowREeGGz/A=", + "iv": "DD7WV6Y8hLGvaG0b", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAdCCqlFhxbKBpv1JClXceH43AqEvcaM6SGfyXx05UJUs=", + "iv": "DD7WV6Y8hLGvaG0b", + "tag": "ctb+ZQjx8FyynvT88rfwkQ==", + "ct": "" + } + }, + { + "plaintext": "a grant sealed by the licence manager", + "ephPrivate": "lzIbkYmIbkKTYDL4dZiAFUFv2h+GWBiqlQfnsVD2An8=", + "iv": "xz2FIBXNM3scOAJb", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAS3L49p4KrGMo/NmHZq8osoPbk2ZCQWLoNIKnpB4T5hg=", + "iv": "xz2FIBXNM3scOAJb", + "tag": "7dC8bYqzsJ/17EC7y6lhCA==", + "ct": "FEMfh4HMvMwqx4QE4YMMR3d3g5rNuxa/+u6u+i/aRLed3q8kBw==" + } + }, + { + "plaintext": "sk-ant-api03-test-0123456789abcdefghij\n", + "ephPrivate": "tAjRQTQHk6nNJLblzdslDbWmyYm8cE6/G4ipXHR0zlE=", + "iv": "BPF1JDQ5mDNhKKr3", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEA1GianODxSXUHYMdUmZLcQztp5ry2/xtwUSeHZWrGFAo=", + "iv": "BPF1JDQ5mDNhKKr3", + "tag": "uiIIFD7LgPmLV3Y6uleSzg==", + "ct": "+maa0GDDOkcyzD0chFQ4a/0NvBrvl23NVqcGv1BEKgmMUenf9HHd" + } + }, + { + "plaintext": "ünïcødé ✓ and a NUL \u0000 inside", + "ephPrivate": "QBYUxuk4jwiWKOPEWeE7v6WxY8BDmZZCDwlY1WJ1PSA=", + "iv": "u76JO8baqokfnjcK", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAePakOoCmmXLtnN3EykbpqfdbfXpkg04F2djxnJWH220=", + "iv": "u76JO8baqokfnjcK", + "tag": "sfBPRnwhfP/p7vlsUx+e9w==", + "ct": "B9yc0FbdKYDtDeX1jmKa+DBP8fPN1O9SbeYBDn9b3+Bq2Q==" + } + }, + { + "plaintext": "a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; a longer value, past one AES block; ", + "ephPrivate": "2VYOyzqLrb+zWUhVpHA9aV3vMAyRWlNPEq2ASf60F9M=", + "iv": "0Gv6ejq07LwbM8cY", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEARSOBczrJ6X1MkxT7Mrd1ryDLmXAMBgYIgpfwBjHz1GA=", + "iv": "0Gv6ejq07LwbM8cY", + "tag": "Vm+E0myXgdMwA6knJDg/+w==", + "ct": "tJwZpUXeQYOu2PG1g9AwIV8RhKKDorUHWQSygEus3xHOpuTwh3+D4j2htIerMB/3AKToQYABeZMyEAOzhL2x+Cg06WlUKrggZjvdX5W7X813KQBgzg0YnnCZTKTYlviDWe1E9TZ3aAauhlcgpBaQXUh/akkDiqeq2Ua5ruY6IGH5HgqWWPYJo3b4zkbArhvdP2HJX9Pn1mQa0BtGMinyutyD0am5vZuemdKGYS1Ao00M81sMXYc2CBy9K48Ofw8GapcmYNHsE0s6NSgNZumnVaR8TQRy/oqEdL3Q6z31hAZMzTdS0ckXg1NCgcgXVuzwNov/D4320E3DxFS3v8Z5p74ZkqAmo/1JcCNJwwQyRCWk/4iJFjP1OKEavOGo+bcp" + } + } + ] +} diff --git a/go/seal/testdata/sealed-by-typescript.json b/go/seal/testdata/sealed-by-typescript.json new file mode 100644 index 0000000..ba811d7 --- /dev/null +++ b/go/seal/testdata/sealed-by-typescript.json @@ -0,0 +1,12 @@ +{ + "privateKey": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VuBCIEIAi2NK/bN+p7cqYUwv/kz72TgLdmJUfOHCDZTrMpQzpS\n-----END PRIVATE KEY-----\n", + "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VuAyEARYvD/w+9ah0KWS9T9pd6Ea6CBymUE48vEVk983QPKyY=\n-----END PUBLIC KEY-----\n", + "box": { + "v": 1, + "eph": "MCowBQYDK2VuAyEAIYlSGrJvF8qSjR1aTFWbEQM/NFbZXrarglN0aRLZZ0E=", + "iv": "ABqT/hMukn6+xpjh", + "tag": "POzmsWTPHSn9xLMMJJ9Akg==", + "ct": "m2u0kuQ0PwrsGelM99Z5aBw6FCd6lFISUbwiK5TzzDw4ZrGtsHEvxytoIeFC" + }, + "plaintext": "a grant sealed by the TypeScript agent module" +} \ No newline at end of file diff --git a/package.json b/package.json index cd796d0..e30efcb 100644 --- a/package.json +++ b/package.json @@ -1,29 +1 @@ -{ - "name": "@novox/mesh-sdk", - "version": "0.1.14", - "description": "The stable spine a Novox Mesh module's own code builds against.", - "type": "module", - "exports": { - ".": "./dist/index.js", - "./contracts": "./dist/contracts/index.js", - "./provisioner": "./dist/provisioner/index.js", - "./tools": "./dist/tools/index.js", - "./messaging": "./dist/messaging/index.js", - "./events": "./dist/events/index.js", - "./primitives": "./dist/primitives/index.js", - "./stdio": "./dist/stdio/index.js", - "./state": "./dist/state/index.js" - }, - "files": [ - "dist" - ], - "scripts": { - "build": "tsc", - "test": "node --test --experimental-strip-types 'test/*.test.ts'", - "prepare": "tsc" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.6.0" - } -} +PKGJSON \ No newline at end of file