@solidus-network/sdk 0.5.0 → 0.6.1
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.
- package/dist/chain/bbs.d.ts +5 -15
- package/dist/chain/bbs.d.ts.map +1 -1
- package/dist/chain/bbs.js +4 -21
- package/dist/chain/bbs.js.map +1 -1
- package/dist/chain/credentials.d.ts.map +1 -1
- package/dist/chain/credentials.js +6 -22
- package/dist/chain/credentials.js.map +1 -1
- package/dist/chain/derivation.d.ts +59 -0
- package/dist/chain/derivation.d.ts.map +1 -0
- package/dist/chain/derivation.js +149 -0
- package/dist/chain/derivation.js.map +1 -0
- package/dist/chain/did.d.ts +6 -0
- package/dist/chain/did.d.ts.map +1 -1
- package/dist/chain/did.js +7 -19
- package/dist/chain/did.js.map +1 -1
- package/dist/chain/index.d.ts.map +1 -1
- package/dist/chain/index.js +15 -1
- package/dist/chain/index.js.map +1 -1
- package/dist/chain/podbundle.d.ts +30 -0
- package/dist/chain/podbundle.d.ts.map +1 -0
- package/dist/chain/podbundle.js +74 -0
- package/dist/chain/podbundle.js.map +1 -0
- package/dist/chain/podsync.d.ts +30 -0
- package/dist/chain/podsync.d.ts.map +1 -0
- package/dist/chain/podsync.js +106 -0
- package/dist/chain/podsync.js.map +1 -0
- package/dist/chain/recovery.d.ts +54 -0
- package/dist/chain/recovery.d.ts.map +1 -0
- package/dist/chain/recovery.js +115 -0
- package/dist/chain/recovery.js.map +1 -0
- package/dist/chain/transaction.d.ts +19 -0
- package/dist/chain/transaction.d.ts.map +1 -1
- package/dist/chain/transaction.js +32 -0
- package/dist/chain/transaction.js.map +1 -1
- package/dist/chain/transfer.d.ts +1 -1
- package/dist/chain/transfer.d.ts.map +1 -1
- package/dist/chain/transfer.js +17 -2
- package/dist/chain/transfer.js.map +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pod credential-bundle encryption (privacy rebuild §5.6).
|
|
3
|
+
*
|
|
4
|
+
* The wallet seals its credential bundle (BBS signature + 8 messages +
|
|
5
|
+
* header + epoch + issuer pubkey) under `pod_wrap_key` — HKDF'd from the
|
|
6
|
+
* seed on the device (§5.1, `derivePodWrapKey`) — before storing it as a
|
|
7
|
+
* Solid resource in the user's pod. Solidus never holds `pod_wrap_key`, so
|
|
8
|
+
* the at-rest resource is opaque to us: the same encrypted blob serves the
|
|
9
|
+
* BD-1 fallback (the wallet reads + decrypts it locally, then ferries the
|
|
10
|
+
* plaintext to the stateless prover) and multi-device sync (a fresh device
|
|
11
|
+
* with the same seed re-derives the key and decrypts).
|
|
12
|
+
*
|
|
13
|
+
* AES-256-GCM via WebCrypto (`globalThis.crypto.subtle`) — identical in the
|
|
14
|
+
* browser wallet and in node. The version tag is bound as AES-GCM
|
|
15
|
+
* additional-authenticated-data, so a format downgrade fails the tag.
|
|
16
|
+
*/
|
|
17
|
+
export declare const POD_BUNDLE_V1 = "solidus.podbundle.v1";
|
|
18
|
+
export interface SealedBundle {
|
|
19
|
+
/** Format tag — bound as GCM AAD. */
|
|
20
|
+
v: string;
|
|
21
|
+
/** 12-byte AES-GCM nonce, hex. Fresh per seal. */
|
|
22
|
+
iv: string;
|
|
23
|
+
/** Ciphertext ‖ 16-byte GCM tag, hex. */
|
|
24
|
+
ct: string;
|
|
25
|
+
}
|
|
26
|
+
/** Seal a plaintext bundle under `podWrapKey`. Fresh random IV per call. */
|
|
27
|
+
export declare function sealCredentialBundle(podWrapKey: Uint8Array, plaintext: Uint8Array): Promise<SealedBundle>;
|
|
28
|
+
/** Open a sealed bundle. Throws on wrong key, tampered ciphertext, or version mismatch. */
|
|
29
|
+
export declare function openCredentialBundle(podWrapKey: Uint8Array, sealed: SealedBundle): Promise<Uint8Array>;
|
|
30
|
+
//# sourceMappingURL=podbundle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"podbundle.d.ts","sourceRoot":"","sources":["../../src/chain/podbundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,aAAa,yBAAyB,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,CAAC,EAAE,MAAM,CAAA;IACT,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAA;IACV,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAA;CACX;AA8BD,4EAA4E;AAC5E,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,YAAY,CAAC,CAavB;AAED,2FAA2F;AAC3F,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,UAAU,CAAC,CAarB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pod credential-bundle encryption (privacy rebuild §5.6).
|
|
3
|
+
*
|
|
4
|
+
* The wallet seals its credential bundle (BBS signature + 8 messages +
|
|
5
|
+
* header + epoch + issuer pubkey) under `pod_wrap_key` — HKDF'd from the
|
|
6
|
+
* seed on the device (§5.1, `derivePodWrapKey`) — before storing it as a
|
|
7
|
+
* Solid resource in the user's pod. Solidus never holds `pod_wrap_key`, so
|
|
8
|
+
* the at-rest resource is opaque to us: the same encrypted blob serves the
|
|
9
|
+
* BD-1 fallback (the wallet reads + decrypts it locally, then ferries the
|
|
10
|
+
* plaintext to the stateless prover) and multi-device sync (a fresh device
|
|
11
|
+
* with the same seed re-derives the key and decrypts).
|
|
12
|
+
*
|
|
13
|
+
* AES-256-GCM via WebCrypto (`globalThis.crypto.subtle`) — identical in the
|
|
14
|
+
* browser wallet and in node. The version tag is bound as AES-GCM
|
|
15
|
+
* additional-authenticated-data, so a format downgrade fails the tag.
|
|
16
|
+
*/
|
|
17
|
+
export const POD_BUNDLE_V1 = 'solidus.podbundle.v1';
|
|
18
|
+
function subtle() {
|
|
19
|
+
const c = globalThis.crypto;
|
|
20
|
+
if (!c?.subtle) {
|
|
21
|
+
throw new Error('WebCrypto SubtleCrypto is unavailable in this runtime');
|
|
22
|
+
}
|
|
23
|
+
return c.subtle;
|
|
24
|
+
}
|
|
25
|
+
function assertKey(key) {
|
|
26
|
+
if (key.length !== 32) {
|
|
27
|
+
throw new Error(`pod wrap key must be 32 bytes (AES-256), got ${key.length}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function toHex(bytes) {
|
|
31
|
+
let out = '';
|
|
32
|
+
for (let i = 0; i < bytes.length; i++)
|
|
33
|
+
out += bytes[i].toString(16).padStart(2, '0');
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function fromHex(hex) {
|
|
37
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
38
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
39
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
40
|
+
}
|
|
41
|
+
return bytes;
|
|
42
|
+
}
|
|
43
|
+
/** Seal a plaintext bundle under `podWrapKey`. Fresh random IV per call. */
|
|
44
|
+
export async function sealCredentialBundle(podWrapKey, plaintext) {
|
|
45
|
+
assertKey(podWrapKey);
|
|
46
|
+
const s = subtle();
|
|
47
|
+
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
48
|
+
const key = await s.importKey('raw', ab(podWrapKey), 'AES-GCM', false, ['encrypt']);
|
|
49
|
+
const ct = new Uint8Array(await s.encrypt({ name: 'AES-GCM', iv: ab(iv), additionalData: ab(aad()) }, key, ab(plaintext)));
|
|
50
|
+
return { v: POD_BUNDLE_V1, iv: toHex(iv), ct: toHex(ct) };
|
|
51
|
+
}
|
|
52
|
+
/** Open a sealed bundle. Throws on wrong key, tampered ciphertext, or version mismatch. */
|
|
53
|
+
export async function openCredentialBundle(podWrapKey, sealed) {
|
|
54
|
+
assertKey(podWrapKey);
|
|
55
|
+
if (sealed.v !== POD_BUNDLE_V1) {
|
|
56
|
+
throw new Error(`unsupported sealed-bundle version ${sealed.v}`);
|
|
57
|
+
}
|
|
58
|
+
const s = subtle();
|
|
59
|
+
const key = await s.importKey('raw', ab(podWrapKey), 'AES-GCM', false, ['decrypt']);
|
|
60
|
+
const pt = await s.decrypt({ name: 'AES-GCM', iv: ab(fromHex(sealed.iv)), additionalData: ab(aad()) }, key, ab(fromHex(sealed.ct)));
|
|
61
|
+
return new Uint8Array(pt);
|
|
62
|
+
}
|
|
63
|
+
function aad() {
|
|
64
|
+
return new TextEncoder().encode(POD_BUNDLE_V1);
|
|
65
|
+
}
|
|
66
|
+
// WebCrypto wants a plain-ArrayBuffer-backed source; copy into a fresh
|
|
67
|
+
// ArrayBuffer so a SharedArrayBuffer-backed input can't leak in and the
|
|
68
|
+
// current lib.dom `ArrayBufferView<ArrayBuffer>` constraint is satisfied.
|
|
69
|
+
function ab(bytes) {
|
|
70
|
+
const out = new ArrayBuffer(bytes.byteLength);
|
|
71
|
+
new Uint8Array(out).set(bytes);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=podbundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"podbundle.js","sourceRoot":"","sources":["../../src/chain/podbundle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SealedBundle } from './podbundle.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic opaque resource locator for the pod bundle:
|
|
4
|
+
* `hex(HKDF-SHA-512(seed64, ∅, "solidus.pod.bundlelocator.v1", 16))`.
|
|
5
|
+
* Seed-bound (device B finds it) but non-correlatable (16 opaque bytes).
|
|
6
|
+
*/
|
|
7
|
+
export declare function derivePodBundleLocator(seed64: Uint8Array): string;
|
|
8
|
+
/** Abstract pod resource store — put/get a sealed bundle by locator. */
|
|
9
|
+
export interface PodBundleClient {
|
|
10
|
+
put(locator: string, sealed: SealedBundle): Promise<void>;
|
|
11
|
+
get(locator: string): Promise<SealedBundle | null>;
|
|
12
|
+
}
|
|
13
|
+
/** Seal-and-store: write the sealed bundle to the seed's pod locator. */
|
|
14
|
+
export declare function syncBundleToPod(client: PodBundleClient, seed64: Uint8Array, sealed: SealedBundle): Promise<void>;
|
|
15
|
+
/** Fetch the sealed bundle for a seed (null if none synced). Caller decrypts. */
|
|
16
|
+
export declare function restoreBundleFromPod(client: PodBundleClient, seed64: Uint8Array): Promise<SealedBundle | null>;
|
|
17
|
+
export declare function createInMemoryPodBundleClient(): PodBundleClient;
|
|
18
|
+
/**
|
|
19
|
+
* A pod bundle client backed by an HTTP Solid resource:
|
|
20
|
+
* `PUT/GET {baseUrl}/credentials/kyc/{locator}.bundle.enc`. `authToken` is the
|
|
21
|
+
* wallet's Solid access token; `fetchImpl` is injectable for tests. A 404 on
|
|
22
|
+
* GET means "no bundle yet" → null (not an error). Only the sealed (opaque)
|
|
23
|
+
* bytes ever leave the device.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createHttpPodBundleClient(opts: {
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
authToken?: string;
|
|
28
|
+
fetchImpl?: typeof fetch;
|
|
29
|
+
}): PodBundleClient;
|
|
30
|
+
//# sourceMappingURL=podsync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"podsync.d.ts","sourceRoot":"","sources":["../../src/chain/podsync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAiBlD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAGjE;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;CACnD;AAED,yEAAyE;AACzE,wBAAsB,eAAe,CACnC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,iFAAiF;AACjF,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAE9B;AAMD,wBAAgB,6BAA6B,IAAI,eAAe,CAU/D;AAMD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,GAAG,eAAe,CA0BlB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pod multi-device sync (privacy rebuild §5.6).
|
|
3
|
+
*
|
|
4
|
+
* The single AES-256-GCM bundle sealed under `pod_wrap_key` (see
|
|
5
|
+
* `podbundle.ts`) doubles as multi-device continuity: a fresh device with the
|
|
6
|
+
* SAME seed re-derives both the wrap key AND a deterministic resource locator,
|
|
7
|
+
* pulls the sealed resource from the pod, and decrypts it — reconstructing a
|
|
8
|
+
* working credential with no prior local state.
|
|
9
|
+
*
|
|
10
|
+
* The locator is HKDF-derived from the seed so device B knows where to look
|
|
11
|
+
* without any prior coordination, yet it is an opaque 16-byte id — not a DID,
|
|
12
|
+
* not correlatable to identity. A different seed derives a different locator
|
|
13
|
+
* and cannot even find (let alone read) the resource.
|
|
14
|
+
*
|
|
15
|
+
* NON-BLOCKING (§5.6): nothing here is on the core unlinkability path. Proof
|
|
16
|
+
* generation is pure local crypto over the bundle and never calls the pod, so
|
|
17
|
+
* an offline/unhealthy Solid server cannot break a presentation. Sync is a
|
|
18
|
+
* convenience layer on top.
|
|
19
|
+
*/
|
|
20
|
+
import { hkdf } from '@noble/hashes/hkdf';
|
|
21
|
+
import { sha512 } from '@noble/hashes/sha512';
|
|
22
|
+
import { POD_BUNDLE_V1 } from './podbundle.js';
|
|
23
|
+
const LOCATOR_INFO_TAG = 'solidus.pod.bundlelocator.v1';
|
|
24
|
+
function assertSeed64(seed64) {
|
|
25
|
+
if (seed64.length !== 64) {
|
|
26
|
+
throw new Error(`seed must be the 64-byte BIP-39 seed, got ${seed64.length} bytes`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function toHex(bytes) {
|
|
30
|
+
let out = '';
|
|
31
|
+
for (let i = 0; i < bytes.length; i++)
|
|
32
|
+
out += bytes[i].toString(16).padStart(2, '0');
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Deterministic opaque resource locator for the pod bundle:
|
|
37
|
+
* `hex(HKDF-SHA-512(seed64, ∅, "solidus.pod.bundlelocator.v1", 16))`.
|
|
38
|
+
* Seed-bound (device B finds it) but non-correlatable (16 opaque bytes).
|
|
39
|
+
*/
|
|
40
|
+
export function derivePodBundleLocator(seed64) {
|
|
41
|
+
assertSeed64(seed64);
|
|
42
|
+
return toHex(hkdf(sha512, seed64, new Uint8Array(0), new TextEncoder().encode(LOCATOR_INFO_TAG), 16));
|
|
43
|
+
}
|
|
44
|
+
/** Seal-and-store: write the sealed bundle to the seed's pod locator. */
|
|
45
|
+
export async function syncBundleToPod(client, seed64, sealed) {
|
|
46
|
+
await client.put(derivePodBundleLocator(seed64), sealed);
|
|
47
|
+
}
|
|
48
|
+
/** Fetch the sealed bundle for a seed (null if none synced). Caller decrypts. */
|
|
49
|
+
export async function restoreBundleFromPod(client, seed64) {
|
|
50
|
+
return client.get(derivePodBundleLocator(seed64));
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// In-memory client — tests + single-process use.
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
export function createInMemoryPodBundleClient() {
|
|
56
|
+
const store = new Map();
|
|
57
|
+
return {
|
|
58
|
+
async put(locator, sealed) {
|
|
59
|
+
store.set(locator, sealed);
|
|
60
|
+
},
|
|
61
|
+
async get(locator) {
|
|
62
|
+
return store.get(locator) ?? null;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// HTTP (Solid resource) client.
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* A pod bundle client backed by an HTTP Solid resource:
|
|
71
|
+
* `PUT/GET {baseUrl}/credentials/kyc/{locator}.bundle.enc`. `authToken` is the
|
|
72
|
+
* wallet's Solid access token; `fetchImpl` is injectable for tests. A 404 on
|
|
73
|
+
* GET means "no bundle yet" → null (not an error). Only the sealed (opaque)
|
|
74
|
+
* bytes ever leave the device.
|
|
75
|
+
*/
|
|
76
|
+
export function createHttpPodBundleClient(opts) {
|
|
77
|
+
const doFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
78
|
+
if (!doFetch)
|
|
79
|
+
throw new Error('no fetch implementation available');
|
|
80
|
+
const base = opts.baseUrl.replace(/\/$/, '');
|
|
81
|
+
const url = (locator) => `${base}/credentials/kyc/${locator}.bundle.enc`;
|
|
82
|
+
const headers = (extra = {}) => opts.authToken ? { ...extra, authorization: `Bearer ${opts.authToken}` } : extra;
|
|
83
|
+
return {
|
|
84
|
+
async put(locator, sealed) {
|
|
85
|
+
const res = await doFetch(url(locator), {
|
|
86
|
+
method: 'PUT',
|
|
87
|
+
headers: headers({ 'content-type': 'application/json' }),
|
|
88
|
+
body: JSON.stringify(sealed),
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok)
|
|
91
|
+
throw new Error(`pod PUT failed: ${res.status}`);
|
|
92
|
+
},
|
|
93
|
+
async get(locator) {
|
|
94
|
+
const res = await doFetch(url(locator), { method: 'GET', headers: headers() });
|
|
95
|
+
if (res.status === 404)
|
|
96
|
+
return null;
|
|
97
|
+
if (!res.ok)
|
|
98
|
+
throw new Error(`pod GET failed: ${res.status}`);
|
|
99
|
+
const body = (await res.json());
|
|
100
|
+
if (body?.v !== POD_BUNDLE_V1)
|
|
101
|
+
throw new Error('pod resource is not a valid sealed bundle');
|
|
102
|
+
return body;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=podsync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"podsync.js","sourceRoot":"","sources":["../../src/chain/podsync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ChainTransaction } from './transaction.js';
|
|
2
|
+
export interface GuardianApproval {
|
|
3
|
+
guardian_did: string;
|
|
4
|
+
signature: number[];
|
|
5
|
+
}
|
|
6
|
+
export interface RecoveryPolicy {
|
|
7
|
+
guardians: string[];
|
|
8
|
+
threshold: number;
|
|
9
|
+
delay_blocks: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The 32-byte BLAKE3 message a guardian signs to approve a recovery. Mirrors the Rust
|
|
13
|
+
* `recovery_signing_message`: BLAKE3( "solidus.did.recover.v1" ‖ (network.len() as u32 LE) ‖ network
|
|
14
|
+
* ‖ (subject_did.len() as u32 LE) ‖ subject_did ‖ new_public_key[32] ‖ recovery_nonce u64 LE ).
|
|
15
|
+
*/
|
|
16
|
+
export declare function recoverySigningMessage(network: string, subjectDid: string, newPublicKey: Uint8Array, recoveryNonce: number | bigint): Uint8Array;
|
|
17
|
+
/**
|
|
18
|
+
* Sign the recovery message as a guardian. Returns the 64-byte Ed25519 signature as a number[],
|
|
19
|
+
* ready to drop into a `GuardianApproval`. The guardian signs the 32-byte hash directly (matching
|
|
20
|
+
* the Rust executor, which recomputes the same hash and verifies against the guardian's on-chain key).
|
|
21
|
+
*/
|
|
22
|
+
export declare function signAsGuardian(guardianPrivateKey: string | Uint8Array, params: {
|
|
23
|
+
network: string;
|
|
24
|
+
subjectDid: string;
|
|
25
|
+
newPublicKey: Uint8Array;
|
|
26
|
+
recoveryNonce: number | bigint;
|
|
27
|
+
}): Promise<number[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Build a signed `DidRecover` transaction. `signerPrivateKey` is the NEW key — the chain enforces
|
|
30
|
+
* `sender_pubkey == new_public_key`, so this throws if the supplied `newPublicKey` is not the
|
|
31
|
+
* signer's own public key (mirroring that guard before a doomed submit).
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildRecover(opts: {
|
|
34
|
+
signerPrivateKey: string;
|
|
35
|
+
subjectDid: string;
|
|
36
|
+
newPublicKey: Uint8Array;
|
|
37
|
+
approvals: GuardianApproval[];
|
|
38
|
+
nonce: number;
|
|
39
|
+
}): Promise<ChainTransaction>;
|
|
40
|
+
/** Relay a signed `DidRecover` tx and poll its receipt. Mirrors `did.submitCreate`. */
|
|
41
|
+
export declare function submitRecover(rpc: {
|
|
42
|
+
call<T>(method: string, params: unknown[]): Promise<T>;
|
|
43
|
+
}, signedTx: ChainTransaction): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Build a signed `DidUpdate` that sets the DID's recovery policy (a k-of-n guardian set). The signer
|
|
46
|
+
* must currently control the DID. Patch key order = Rust field order: guardians, threshold, delay_blocks.
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildSetRecoveryPolicy(opts: {
|
|
49
|
+
signerPrivateKey: string;
|
|
50
|
+
did: string;
|
|
51
|
+
policy: RecoveryPolicy;
|
|
52
|
+
nonce: number;
|
|
53
|
+
}): Promise<ChainTransaction>;
|
|
54
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../src/chain/recovery.ts"],"names":[],"mappings":"AAYA,OAAO,EAA6C,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAEnG,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAoBD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,UAAU,EACxB,aAAa,EAAE,MAAM,GAAG,MAAM,GAC7B,UAAU,CAaZ;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,kBAAkB,EAAE,MAAM,GAAG,UAAU,EACvC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,UAAU,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GACxG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKnB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,UAAU,CAAA;IACxB,SAAS,EAAE,gBAAgB,EAAE,CAAA;IAC7B,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe5B;AAED,uFAAuF;AACvF,wBAAsB,aAAa,CACjC,GAAG,EAAE;IAAE,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EAC/D,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,gBAAgB,EAAE,MAAM,CAAA;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,cAAc,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB5B"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DID social recovery — SDK surface (sub-project #2).
|
|
3
|
+
*
|
|
4
|
+
* Byte-for-byte compatible with the Rust protocol primitive (sub-project #1): the recovery signing
|
|
5
|
+
* message and the `DidRecover` / `SetRecoveryPolicy` payload JSON must match the Rust exactly, or a
|
|
6
|
+
* guardian signature built here will not verify on chain. Parity is locked by shared test vectors
|
|
7
|
+
* generated from Rust (see recovery.test.ts; vectors from solidus-txns commit 21377537).
|
|
8
|
+
*/
|
|
9
|
+
import { blake3 } from '@noble/hashes/blake3';
|
|
10
|
+
import * as ed from '@noble/ed25519';
|
|
11
|
+
// Importing transaction.ts also runs its `ed.etc.sha512Sync = …` side-effect, so @noble/ed25519 is
|
|
12
|
+
// configured for both this module and any consumer that only imports recovery.
|
|
13
|
+
import { buildTransaction, hexToBytes, pollReceipt } from './transaction.js';
|
|
14
|
+
const RECOVER_DOMAIN_SEP = new TextEncoder().encode('solidus.did.recover.v1');
|
|
15
|
+
function u32le(n) {
|
|
16
|
+
const b = new Uint8Array(4);
|
|
17
|
+
new DataView(b.buffer).setUint32(0, n, true);
|
|
18
|
+
return b;
|
|
19
|
+
}
|
|
20
|
+
function u64le(n) {
|
|
21
|
+
const b = new Uint8Array(8);
|
|
22
|
+
new DataView(b.buffer).setBigUint64(0, BigInt(n), true);
|
|
23
|
+
return b;
|
|
24
|
+
}
|
|
25
|
+
function bytesEqual(a, b) {
|
|
26
|
+
if (a.length !== b.length)
|
|
27
|
+
return false;
|
|
28
|
+
for (let i = 0; i < a.length; i++)
|
|
29
|
+
if (a[i] !== b[i])
|
|
30
|
+
return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The 32-byte BLAKE3 message a guardian signs to approve a recovery. Mirrors the Rust
|
|
35
|
+
* `recovery_signing_message`: BLAKE3( "solidus.did.recover.v1" ‖ (network.len() as u32 LE) ‖ network
|
|
36
|
+
* ‖ (subject_did.len() as u32 LE) ‖ subject_did ‖ new_public_key[32] ‖ recovery_nonce u64 LE ).
|
|
37
|
+
*/
|
|
38
|
+
export function recoverySigningMessage(network, subjectDid, newPublicKey, recoveryNonce) {
|
|
39
|
+
if (newPublicKey.length !== 32)
|
|
40
|
+
throw new Error('newPublicKey must be 32 bytes');
|
|
41
|
+
const net = new TextEncoder().encode(network);
|
|
42
|
+
const sub = new TextEncoder().encode(subjectDid);
|
|
43
|
+
const parts = [RECOVER_DOMAIN_SEP, u32le(net.length), net, u32le(sub.length), sub, newPublicKey, u64le(recoveryNonce)];
|
|
44
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
45
|
+
const buf = new Uint8Array(total);
|
|
46
|
+
let off = 0;
|
|
47
|
+
for (const p of parts) {
|
|
48
|
+
buf.set(p, off);
|
|
49
|
+
off += p.length;
|
|
50
|
+
}
|
|
51
|
+
return blake3(buf);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Sign the recovery message as a guardian. Returns the 64-byte Ed25519 signature as a number[],
|
|
55
|
+
* ready to drop into a `GuardianApproval`. The guardian signs the 32-byte hash directly (matching
|
|
56
|
+
* the Rust executor, which recomputes the same hash and verifies against the guardian's on-chain key).
|
|
57
|
+
*/
|
|
58
|
+
export async function signAsGuardian(guardianPrivateKey, params) {
|
|
59
|
+
const sk = typeof guardianPrivateKey === 'string' ? hexToBytes(guardianPrivateKey) : guardianPrivateKey;
|
|
60
|
+
const msg = recoverySigningMessage(params.network, params.subjectDid, params.newPublicKey, params.recoveryNonce);
|
|
61
|
+
const sig = await ed.signAsync(msg, sk);
|
|
62
|
+
return Array.from(sig);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build a signed `DidRecover` transaction. `signerPrivateKey` is the NEW key — the chain enforces
|
|
66
|
+
* `sender_pubkey == new_public_key`, so this throws if the supplied `newPublicKey` is not the
|
|
67
|
+
* signer's own public key (mirroring that guard before a doomed submit).
|
|
68
|
+
*/
|
|
69
|
+
export async function buildRecover(opts) {
|
|
70
|
+
if (opts.newPublicKey.length !== 32)
|
|
71
|
+
throw new Error('newPublicKey must be 32 bytes');
|
|
72
|
+
const signerPub = await ed.getPublicKeyAsync(hexToBytes(opts.signerPrivateKey));
|
|
73
|
+
if (!bytesEqual(signerPub, opts.newPublicKey)) {
|
|
74
|
+
throw new Error('buildRecover: newPublicKey must equal the signer public key (the recovery envelope sender == the new key)');
|
|
75
|
+
}
|
|
76
|
+
// Key order = Rust struct-field order: did, new_public_key, approvals (locked by the parity test).
|
|
77
|
+
const payload = {
|
|
78
|
+
DidRecover: {
|
|
79
|
+
did: opts.subjectDid,
|
|
80
|
+
new_public_key: Array.from(opts.newPublicKey),
|
|
81
|
+
approvals: opts.approvals,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return buildTransaction(opts.signerPrivateKey, opts.nonce, payload);
|
|
85
|
+
}
|
|
86
|
+
/** Relay a signed `DidRecover` tx and poll its receipt. Mirrors `did.submitCreate`. */
|
|
87
|
+
export async function submitRecover(rpc, signedTx) {
|
|
88
|
+
const txHash = await rpc.call('solidus_sendTransaction', [JSON.stringify(signedTx)]);
|
|
89
|
+
const receipt = await pollReceipt(rpc, txHash);
|
|
90
|
+
if (!receipt.status.startsWith('success')) {
|
|
91
|
+
throw new Error(`DidRecover failed: ${receipt.status}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build a signed `DidUpdate` that sets the DID's recovery policy (a k-of-n guardian set). The signer
|
|
96
|
+
* must currently control the DID. Patch key order = Rust field order: guardians, threshold, delay_blocks.
|
|
97
|
+
*/
|
|
98
|
+
export async function buildSetRecoveryPolicy(opts) {
|
|
99
|
+
const payload = {
|
|
100
|
+
DidUpdate: {
|
|
101
|
+
did: opts.did,
|
|
102
|
+
patches: [
|
|
103
|
+
{
|
|
104
|
+
SetRecoveryPolicy: {
|
|
105
|
+
guardians: opts.policy.guardians,
|
|
106
|
+
threshold: opts.policy.threshold,
|
|
107
|
+
delay_blocks: opts.policy.delay_blocks,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
return buildTransaction(opts.signerPrivateKey, opts.nonce, payload);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/chain/recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,25 @@ export interface RpcReceipt {
|
|
|
32
32
|
export declare function pollReceipt(rpc: {
|
|
33
33
|
call<T>(method: string, params: unknown[]): Promise<T>;
|
|
34
34
|
}, txHash: string): Promise<RpcReceipt>;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch the signer's nonce, build, submit, and confirm a one-shot
|
|
37
|
+
* transaction — retrying when the receipt reports `invalid nonce`.
|
|
38
|
+
*
|
|
39
|
+
* `solidus_getNonce` can lag the previous transaction's receipt by a
|
|
40
|
+
* beat, so an immediate follow-up tx from the same signer races it
|
|
41
|
+
* (observed live 2026-07-13: create → deactivate failed with
|
|
42
|
+
* "invalid nonce: expected 1, got 0"). The failed receipt names the
|
|
43
|
+
* expected nonce, so the retry is deterministic — no sleep-and-hope.
|
|
44
|
+
*
|
|
45
|
+
* Exactly ONE retry, on purpose: the observed race is a one-beat lag,
|
|
46
|
+
* and a caller that abandons this promise (e.g. behind a 5s
|
|
47
|
+
* withChainTimeout) cannot cancel it — every extra attempt widens the
|
|
48
|
+
* window in which an abandoned loop and a caller-level retry both land
|
|
49
|
+
* a non-idempotent payload (duplicate CredentialIssue) on chain.
|
|
50
|
+
*/
|
|
51
|
+
export declare function submitWithNonceRetry(rpc: {
|
|
52
|
+
call<T>(method: string, params: unknown[]): Promise<T>;
|
|
53
|
+
}, signerKey: string, payload: unknown, label: string): Promise<RpcReceipt>;
|
|
35
54
|
/**
|
|
36
55
|
* Build and sign a transaction for the Solidus chain.
|
|
37
56
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/chain/transaction.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAA;CACxD;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE;IAAE,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EAC/D,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,CAAC,CAQrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,gBAAgB,CAAC,CAuB3B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAIlE;AAID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAMlD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIpD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhD"}
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/chain/transaction.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAA;CACxD;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE;IAAE,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EAC/D,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,CAAC,CAQrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE;IAAE,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EAC/D,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,UAAU,CAAC,CAcrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,gBAAgB,CAAC,CAuB3B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAIlE;AAID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAMlD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIpD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhD"}
|
|
@@ -35,6 +35,38 @@ export async function pollReceipt(rpc, txHash) {
|
|
|
35
35
|
}
|
|
36
36
|
throw new Error(`Transaction ${txHash} not confirmed after 10 seconds`);
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Fetch the signer's nonce, build, submit, and confirm a one-shot
|
|
40
|
+
* transaction — retrying when the receipt reports `invalid nonce`.
|
|
41
|
+
*
|
|
42
|
+
* `solidus_getNonce` can lag the previous transaction's receipt by a
|
|
43
|
+
* beat, so an immediate follow-up tx from the same signer races it
|
|
44
|
+
* (observed live 2026-07-13: create → deactivate failed with
|
|
45
|
+
* "invalid nonce: expected 1, got 0"). The failed receipt names the
|
|
46
|
+
* expected nonce, so the retry is deterministic — no sleep-and-hope.
|
|
47
|
+
*
|
|
48
|
+
* Exactly ONE retry, on purpose: the observed race is a one-beat lag,
|
|
49
|
+
* and a caller that abandons this promise (e.g. behind a 5s
|
|
50
|
+
* withChainTimeout) cannot cancel it — every extra attempt widens the
|
|
51
|
+
* window in which an abandoned loop and a caller-level retry both land
|
|
52
|
+
* a non-idempotent payload (duplicate CredentialIssue) on chain.
|
|
53
|
+
*/
|
|
54
|
+
export async function submitWithNonceRetry(rpc, signerKey, payload, label) {
|
|
55
|
+
const address = await getAddressFromKey(signerKey);
|
|
56
|
+
let nonce = await rpc.call('solidus_getNonce', [address]);
|
|
57
|
+
for (let attempt = 0;; attempt++) {
|
|
58
|
+
const tx = await buildTransaction(signerKey, nonce, payload);
|
|
59
|
+
const txHash = await rpc.call('solidus_sendTransaction', [JSON.stringify(tx)]);
|
|
60
|
+
const receipt = await pollReceipt(rpc, txHash);
|
|
61
|
+
if (receipt.status.startsWith('success'))
|
|
62
|
+
return receipt;
|
|
63
|
+
const expected = /invalid nonce: expected (\d+)/.exec(receipt.status);
|
|
64
|
+
if (expected === null || attempt >= 1) {
|
|
65
|
+
throw new Error(`${label} failed: ${receipt.status}`);
|
|
66
|
+
}
|
|
67
|
+
nonce = Number(expected[1]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
38
70
|
/**
|
|
39
71
|
* Build and sign a transaction for the Solidus chain.
|
|
40
72
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../../src/chain/transaction.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../../src/chain/transaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,yDAAyD;AACzD,EAAE,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,GAAG,CAAe,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAsB5E;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,GAA+D,EAC/D,MAAc;IAEd,MAAM,WAAW,GAAG,EAAE,CAAA,CAAC,mBAAmB;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAAoB,oBAAoB,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;QACjF,IAAI,OAAO;YAAE,OAAO,OAAO,CAAA;QAC3B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;IAC1D,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,iCAAiC,CAAC,CAAA;AACzE,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAA+D,EAC/D,SAAiB,EACjB,OAAgB,EAChB,KAAa;IAEb,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAA;IAClD,IAAI,KAAK,GAAG,MAAM,GAAG,CAAC,IAAI,CAAS,kBAAkB,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IACjE,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;QAC5D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAS,yBAAyB,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACtF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC9C,IAAI,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,OAAO,CAAA;QACxD,MAAM,QAAQ,GAAG,+BAA+B,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACrE,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QACvD,CAAC;QACD,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,aAAqB,EACrB,KAAa,EACb,OAAgB;IAEhB,MAAM,UAAU,GAAG,UAAU,CAAC,aAAa,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAA;IAExD,mEAAmE;IACnE,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;IACpC,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA,CAAC,gBAAgB;IACrF,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IAEtE,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;IAC3F,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;IAC1B,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,CAAA;IAC1C,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;IAEhE,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IACpC,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;IAE7D,OAAO;QACL,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;QACpC,KAAK;QACL,OAAO;QACP,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;KACjC,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,aAAqB;IAC3D,MAAM,UAAU,GAAG,UAAU,CAAC,aAAa,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAA;IACxD,OAAO,oBAAoB,CAAC,SAAS,CAAC,CAAA;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAqB;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;IAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACtC,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;AAClC,CAAC;AAED,sCAAsC;AAEtC,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACtD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAiB;IAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAA;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AACpC,CAAC"}
|
package/dist/chain/transfer.d.ts
CHANGED
|
@@ -22,5 +22,5 @@ export interface TransferOptions {
|
|
|
22
22
|
/** Optional nonce override; fetched via `solidus_getNonce` when omitted. */
|
|
23
23
|
nonce?: number;
|
|
24
24
|
}
|
|
25
|
-
export declare function createChainTransfer(rpc: RpcClient): (opts: TransferOptions) => Promise<RpcReceipt>;
|
|
25
|
+
export declare function createChainTransfer(rpc: RpcClient, network: string): (opts: TransferOptions) => Promise<RpcReceipt>;
|
|
26
26
|
//# sourceMappingURL=transfer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../src/chain/transfer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAEjF,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,CAAA;IACxB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../src/chain/transfer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAEjF,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,CAAA;IACxB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,IAOlC,MAAM,eAAe,KAAG,OAAO,CAAC,UAAU,CAAC,CA6C3E"}
|
package/dist/chain/transfer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import bs58 from 'bs58';
|
|
2
2
|
import { buildTransaction, pollReceipt } from './transaction.js';
|
|
3
|
-
export function createChainTransfer(rpc) {
|
|
3
|
+
export function createChainTransfer(rpc, network) {
|
|
4
4
|
/**
|
|
5
5
|
* Submit a `Transfer` transaction, poll its receipt, and return it.
|
|
6
6
|
* Throws on a failed receipt. The signer's address is derived from
|
|
@@ -15,9 +15,24 @@ export function createChainTransfer(rpc) {
|
|
|
15
15
|
if (toBytes.length !== 20) {
|
|
16
16
|
throw new Error(`chain.transfer: toAddress must decode to 20 bytes (got ${toBytes.length}); pass the base58 address, not the public key`);
|
|
17
17
|
}
|
|
18
|
-
// Resolve nonce.
|
|
19
18
|
const { getAddressFromKey } = await import('./transaction.js');
|
|
20
19
|
const senderAddress = await getAddressFromKey(signerPrivateKey);
|
|
20
|
+
// Identity/value separation (interim convention — see
|
|
21
|
+
// .claude/plans/2026-06-11-did-payment-unlinkability.md): the SDK never
|
|
22
|
+
// signs a value Transfer from a DID/identity address. If the signer's
|
|
23
|
+
// address has a DID registered on chain, refuse — value must move with a
|
|
24
|
+
// separate payment account so that resolving a DID never exposes payment
|
|
25
|
+
// history. Relayer/sponsor funding accounts are not DIDs, so they pass.
|
|
26
|
+
// The protocol-level decouple that makes this guard redundant is roadmapped
|
|
27
|
+
// in .claude/plans/2026-06-26-did-value-account-decoupling.md.
|
|
28
|
+
const senderDid = `did:solidus:${network}:${senderAddress}`;
|
|
29
|
+
const existingDid = await rpc.call('solidus_didResolve', [senderDid]);
|
|
30
|
+
if (existingDid != null) {
|
|
31
|
+
throw new Error(`chain.transfer: refusing to sign a Transfer from identity address ${senderAddress} — ` +
|
|
32
|
+
`it has a DID registered. Move value with a separate payment account ` +
|
|
33
|
+
`(identity and value must stay unlinkable).`);
|
|
34
|
+
}
|
|
35
|
+
// Resolve nonce.
|
|
21
36
|
const nonce = opts.nonce ?? (await rpc.call('solidus_getNonce', [senderAddress]));
|
|
22
37
|
const payload = { Transfer: { to: Array.from(toBytes), amount } };
|
|
23
38
|
const tx = await buildTransaction(signerPrivateKey, nonce, payload);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer.js","sourceRoot":"","sources":["../../src/chain/transfer.ts"],"names":[],"mappings":"AAaA,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAmB,MAAM,kBAAkB,CAAA;AAajF,MAAM,UAAU,mBAAmB,CAAC,GAAc;
|
|
1
|
+
{"version":3,"file":"transfer.js","sourceRoot":"","sources":["../../src/chain/transfer.ts"],"names":[],"mappings":"AAaA,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAmB,MAAM,kBAAkB,CAAA;AAajF,MAAM,UAAU,mBAAmB,CAAC,GAAc,EAAE,OAAe;IACjE;;;;;OAKG;IACH,OAAO,KAAK,UAAU,QAAQ,CAAC,IAAqB;QAClD,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;QACpD,wEAAwE;QACxE,iEAAiE;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QACtC,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,0DAA0D,OAAO,CAAC,MAAM,gDAAgD,CACzH,CAAA;QACH,CAAC;QAED,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QAC9D,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,gBAAgB,CAAC,CAAA;QAE/D,sDAAsD;QACtD,wEAAwE;QACxE,sEAAsE;QACtE,yEAAyE;QACzE,yEAAyE;QACzE,wEAAwE;QACxE,4EAA4E;QAC5E,+DAA+D;QAC/D,MAAM,SAAS,GAAG,eAAe,OAAO,IAAI,aAAa,EAAE,CAAA;QAC3D,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,IAAI,CAAU,oBAAoB,EAAE,CAAC,SAAS,CAAC,CAAC,CAAA;QAC9E,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,qEAAqE,aAAa,KAAK;gBACrF,sEAAsE;gBACtE,4CAA4C,CAC/C,CAAA;QACH,CAAC;QAED,iBAAiB;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAS,kBAAkB,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAA;QAEzF,MAAM,OAAO,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,CAAA;QACjE,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;QAEnE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAS,yBAAyB,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACtF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC9C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QACvD,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC"}
|