@totemsdk/identity 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Identity rotation — produces a RotationClaim.
3
+ */
4
+ import { createIdentityClaim } from './claims.js';
5
+ export function rotateIdentity(opts) {
6
+ const { issuer, subject, newAddress } = opts;
7
+ return createIdentityClaim({
8
+ type: 'rotates_to',
9
+ issuer,
10
+ subject,
11
+ object: newAddress,
12
+ payload: {},
13
+ issuedAt: opts.issuedAt,
14
+ });
15
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Claim signing using WOTS primitives from @totemsdk/core.
3
+ */
4
+ import type { IdentityClaim, SignedIdentityClaim } from './types.js';
5
+ export declare function claimDigest(claim: IdentityClaim): Uint8Array;
6
+ export declare function signIdentityClaim(claim: IdentityClaim, seed: Uint8Array, keyIndex: number): Promise<SignedIdentityClaim>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Claim signing using WOTS primitives from @totemsdk/core.
3
+ */
4
+ import { sha3_256 } from '@noble/hashes/sha3.js';
5
+ import { wotsSign, wotsKeypairFromSeed, wotsAddressFromKeypair, bytesToHex, } from '@totemsdk/core';
6
+ import { canonicalJson } from './canonical.js';
7
+ export function claimDigest(claim) {
8
+ const canonical = canonicalJson(claim);
9
+ return sha3_256(new TextEncoder().encode(canonical));
10
+ }
11
+ export async function signIdentityClaim(claim, seed, keyIndex) {
12
+ const digest = claimDigest(claim);
13
+ const sigBytes = wotsSign(seed, keyIndex, digest);
14
+ const kp = wotsKeypairFromSeed(seed, keyIndex);
15
+ const address = wotsAddressFromKeypair(kp);
16
+ return {
17
+ claim,
18
+ proof: {
19
+ address,
20
+ publicKey: bytesToHex(kp.pk),
21
+ signature: bytesToHex(sigBytes),
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @totemsdk/identity — Type definitions
3
+ *
4
+ * Pure schema — no network, no DHT, no blockchain submission.
5
+ */
6
+ export type IdentityKind = 'person' | 'device' | 'agent' | 'service' | 'organization' | 'sensor' | 'robot' | 'gateway';
7
+ export type IdentityStatus = 'active' | 'rotated' | 'revoked';
8
+ export interface TotemIdentityDocument {
9
+ id: string;
10
+ kind: IdentityKind;
11
+ version: number;
12
+ rootAddress: string;
13
+ controllerAddress: string;
14
+ createdAt: number;
15
+ metadata?: Record<string, unknown>;
16
+ }
17
+ export type IdentityClaimType = 'delegates_to' | 'payment_recipient' | 'service_endpoint' | 'rotates_to' | 'revokes';
18
+ export interface IdentityClaim {
19
+ id: string;
20
+ type: IdentityClaimType;
21
+ issuer: string;
22
+ subject: string;
23
+ object: string;
24
+ issuedAt: number;
25
+ expiresAt?: number;
26
+ payload: Record<string, unknown>;
27
+ }
28
+ export interface SignedIdentityClaim {
29
+ claim: IdentityClaim;
30
+ proof: {
31
+ address: string;
32
+ publicKey: string;
33
+ signature: string;
34
+ message?: string;
35
+ };
36
+ rootIdentityProof?: string;
37
+ }
38
+ export interface IdentityVerifyResult {
39
+ valid: boolean;
40
+ reason?: string;
41
+ signerAddress?: string;
42
+ rootAddress?: string;
43
+ provenAddresses?: string[];
44
+ metadata?: Record<string, unknown>;
45
+ }
46
+ export interface IdentityProofVerifier {
47
+ type: string;
48
+ verify(proof: unknown): Promise<IdentityVerifyResult>;
49
+ }
50
+ export interface IdentityGraph {
51
+ document: TotemIdentityDocument;
52
+ claims: SignedIdentityClaim[];
53
+ }
54
+ export interface ResolvedIdentity {
55
+ document: TotemIdentityDocument;
56
+ status: IdentityStatus;
57
+ rootAddress: string;
58
+ controllerAddress: string;
59
+ controlledAddresses: string[];
60
+ authorizedAddresses: string[];
61
+ delegates: DelegationClaim[];
62
+ paymentRecipients: PaymentRecipientClaim[];
63
+ serviceEndpoints: ServiceEndpointClaim[];
64
+ rotationTarget?: string;
65
+ revokedAt?: number;
66
+ }
67
+ export interface IdentityResolutionResult {
68
+ resolved: ResolvedIdentity | null;
69
+ errors: string[];
70
+ }
71
+ export interface DelegationClaim {
72
+ claimId: string;
73
+ issuer: string;
74
+ subject: string;
75
+ delegatedAddress: string;
76
+ scopes: string[];
77
+ issuedAt: number;
78
+ expiresAt?: number;
79
+ }
80
+ export interface PaymentRecipientClaim {
81
+ claimId: string;
82
+ issuer: string;
83
+ address: string;
84
+ label?: string;
85
+ issuedAt: number;
86
+ expiresAt?: number;
87
+ }
88
+ export interface ServiceEndpointClaim {
89
+ claimId: string;
90
+ issuer: string;
91
+ endpointType: string;
92
+ uri: string;
93
+ issuedAt: number;
94
+ expiresAt?: number;
95
+ }
96
+ export interface RotationClaim {
97
+ claimId: string;
98
+ issuer: string;
99
+ subject: string;
100
+ newAddress: string;
101
+ issuedAt: number;
102
+ }
103
+ export interface RevocationClaim {
104
+ claimId: string;
105
+ issuer: string;
106
+ subject: string;
107
+ reason?: string;
108
+ issuedAt: number;
109
+ }
110
+ export interface ManifestIdentityBinding {
111
+ valid: boolean;
112
+ reason?: string;
113
+ manifestId: string;
114
+ identityId: string;
115
+ signerAddress: string;
116
+ resolvedStatus: IdentityStatus;
117
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @totemsdk/identity — Type definitions
3
+ *
4
+ * Pure schema — no network, no DHT, no blockchain submission.
5
+ */
6
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Claim verification.
3
+ *
4
+ * verifyIdentityClaim recomputes the canonical claim digest from signed.claim
5
+ * using canonicalJson. It does NOT use proof.message as the source of truth —
6
+ * that field is optional debug/display metadata only.
7
+ *
8
+ * Security: proof.address is bound to proof.publicKey by re-deriving the
9
+ * expected address from the public key. Any mismatch fails verification,
10
+ * preventing signer-address spoofing.
11
+ */
12
+ import type { SignedIdentityClaim, IdentityVerifyResult } from './types.js';
13
+ export declare function verifyIdentityClaim(signed: SignedIdentityClaim): IdentityVerifyResult;
package/dist/verify.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Claim verification.
3
+ *
4
+ * verifyIdentityClaim recomputes the canonical claim digest from signed.claim
5
+ * using canonicalJson. It does NOT use proof.message as the source of truth —
6
+ * that field is optional debug/display metadata only.
7
+ *
8
+ * Security: proof.address is bound to proof.publicKey by re-deriving the
9
+ * expected address from the public key. Any mismatch fails verification,
10
+ * preventing signer-address spoofing.
11
+ */
12
+ import { wotsVerifyDigest, hexToBytes, scriptFromWotsPk, scriptToAddress } from '@totemsdk/core';
13
+ import { claimDigest } from './signing.js';
14
+ /**
15
+ * Derive the Minima address from a 32-byte WOTS PKdigest (hex).
16
+ * Returns null if the public key cannot be decoded.
17
+ */
18
+ function addressFromPkDigest(publicKeyHex) {
19
+ try {
20
+ const pkDigest = hexToBytes(publicKeyHex);
21
+ const script = scriptFromWotsPk(pkDigest);
22
+ return scriptToAddress(script);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export function verifyIdentityClaim(signed) {
29
+ const { claim, proof } = signed;
30
+ let sigBytes;
31
+ let pkDigest;
32
+ try {
33
+ sigBytes = hexToBytes(proof.signature);
34
+ pkDigest = hexToBytes(proof.publicKey);
35
+ }
36
+ catch (e) {
37
+ return {
38
+ valid: false,
39
+ reason: `hex decode failed: ${String(e)}`,
40
+ signerAddress: proof.address,
41
+ };
42
+ }
43
+ // Bind proof.address to proof.publicKey — derive expected address from the public key
44
+ // and verify it matches proof.address. This prevents signer-address spoofing.
45
+ const expectedAddress = addressFromPkDigest(proof.publicKey);
46
+ if (expectedAddress === null) {
47
+ return {
48
+ valid: false,
49
+ reason: 'could not derive address from proof.publicKey',
50
+ signerAddress: proof.address,
51
+ };
52
+ }
53
+ if (expectedAddress !== proof.address) {
54
+ return {
55
+ valid: false,
56
+ reason: `proof.address '${proof.address}' does not match address derived from proof.publicKey '${expectedAddress}'`,
57
+ signerAddress: proof.address,
58
+ };
59
+ }
60
+ const digest = claimDigest(claim);
61
+ let sigValid;
62
+ try {
63
+ sigValid = wotsVerifyDigest(sigBytes, digest, pkDigest);
64
+ }
65
+ catch (e) {
66
+ return {
67
+ valid: false,
68
+ reason: `WOTS verify threw: ${String(e)}`,
69
+ signerAddress: proof.address,
70
+ };
71
+ }
72
+ if (!sigValid) {
73
+ return {
74
+ valid: false,
75
+ reason: 'WOTS signature invalid',
76
+ signerAddress: proof.address,
77
+ };
78
+ }
79
+ return {
80
+ valid: true,
81
+ signerAddress: proof.address,
82
+ };
83
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@totemsdk/identity",
3
+ "version": "0.1.0",
4
+ "description": "Canonical identity and claims layer for Totem Edge — who controls a manifest, device, or agent",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "dependencies": {
19
+ "@totemsdk/core": "1.0.9",
20
+ "@totemsdk/manifest": "0.1.0"
21
+ },
22
+ "peerDependencies": {
23
+ "@noble/hashes": ">=1.3.0 <2.0.0"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "@noble/hashes": {
27
+ "optional": false
28
+ }
29
+ },
30
+ "devDependencies": {
31
+ "@noble/hashes": "^1.3.0",
32
+ "@types/jest": "^29.0.0",
33
+ "@types/node": "^20.0.0",
34
+ "jest": "^29.0.0",
35
+ "ts-jest": "^29.0.0",
36
+ "typescript": "^5.0.0",
37
+ "@totemsdk/root-identity": "1.0.5"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "license": "MIT",
46
+ "scripts": {
47
+ "build": "tsc",
48
+ "clean": "rm -rf dist",
49
+ "test": "jest --passWithNoTests"
50
+ }
51
+ }