release(v4.4.0): public accessor for device identity public key
Expose the local device's 32-byte Ed25519 identity public key on Shade so apps can hand it to their own backend at enrollment time for signature verification, key pinning or per-device safety-number computation. Closes the gap that forced consumers to ship placeholder random bytes their backend could store but never verify against. - @shade/sdk Shade.identityPublicKey: Promise<Uint8Array> — getter mirrors the existing fingerprint accessor. Throws pre-init, reflects the current key after rotate(), retired key preserved in retired-identities storage per existing grace-period contract. Private key remains unreachable. - Test in shade-sdk/tests/sdk.test.ts: round-trip match against the underlying storage's signingPublicKey, plus value updates after rotate(). - Lockstep version bump 4.3.0 → 4.4.0 across all 25 packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/cli",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/cli.ts",
|
||||
"bin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/core",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/crypto-web",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/dashboard",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/files",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/inbox-server",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/inbox",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/key-transparency",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/keychain",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/observability",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/observer",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/proto",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/recovery",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/sdk",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -299,6 +299,26 @@ export class Shade {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
/**
|
||||
* The local device's Ed25519 identity public key (32 bytes).
|
||||
*
|
||||
* Stable for the lifetime of the identity. After {@link rotate} this
|
||||
* reflects the new key; the previous key is preserved in retired-
|
||||
* identities storage for the configured grace period.
|
||||
*
|
||||
* Hand this to your application's backend at enrollment time so it
|
||||
* can verify signatures from this device, compute its own safety-
|
||||
* number representation, or pin the key for later attestation. Use
|
||||
* {@link fingerprint} instead for human side-channel comparison.
|
||||
*/
|
||||
get identityPublicKey(): Promise<Uint8Array> {
|
||||
if (!this.initialized) throw new Error('Not initialized');
|
||||
return this.storage.getIdentityKeyPair().then((kp) => {
|
||||
if (!kp) throw new Error('Identity not yet generated');
|
||||
return kp.signingPublicKey;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* `@shade/files` namespace — high-level entry point for E2EE filesystem
|
||||
* RPC. Lazily creates the underlying channel + streams bridges on first
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
MemoryPrekeyStore,
|
||||
PrekeyServerEvents,
|
||||
} from '@shade/server';
|
||||
import { SubtleCryptoProvider } from '@shade/crypto-web';
|
||||
import { SubtleCryptoProvider, MemoryStorage } from '@shade/crypto-web';
|
||||
|
||||
const crypto = new SubtleCryptoProvider();
|
||||
|
||||
@@ -182,6 +182,26 @@ describe('createShade — happy path', () => {
|
||||
const newFp = await alice.fingerprint;
|
||||
expect(newFp).not.toBe(oldFp);
|
||||
});
|
||||
|
||||
test('identityPublicKey exposes the device Ed25519 key and tracks rotation', async () => {
|
||||
const storage = new MemoryStorage();
|
||||
alice = await createShade({ prekeyServer: server.url, address: 'alice', storage });
|
||||
|
||||
const pk = await alice.identityPublicKey;
|
||||
expect(pk).toBeInstanceOf(Uint8Array);
|
||||
expect(pk.length).toBe(32);
|
||||
|
||||
// Matches what the underlying storage holds
|
||||
const stored = await storage.getIdentityKeyPair();
|
||||
expect(stored).not.toBeNull();
|
||||
expect(pk).toEqual(stored!.signingPublicKey);
|
||||
|
||||
// Reflects the new key after rotate (acceptance criteria #3)
|
||||
await alice.rotate();
|
||||
const pkAfter = await alice.identityPublicKey;
|
||||
expect(pkAfter.length).toBe(32);
|
||||
expect(pkAfter).not.toEqual(pk);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createShade — validation', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/server",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/storage-encrypted",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/storage-indexeddb",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/storage-postgres",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/storage-sqlite",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/streams",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/transfer",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/transport-bridge",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/transport-webrtc",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/transport",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/widgets",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
Reference in New Issue
Block a user