@jamscript/client 0.1.0-rc.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/LICENSE +201 -0
- package/README.md +126 -0
- package/dist/abi.d.ts +101 -0
- package/dist/abi.js +18 -0
- package/dist/client.d.ts +69 -0
- package/dist/client.js +339 -0
- package/dist/codec.d.ts +8 -0
- package/dist/codec.js +314 -0
- package/dist/crypto.d.ts +65 -0
- package/dist/crypto.js +233 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/matrix.d.ts +23 -0
- package/dist/matrix.js +31 -0
- package/dist/proof.d.ts +1 -0
- package/dist/proof.js +125 -0
- package/dist/rpc.d.ts +115 -0
- package/dist/rpc.js +83 -0
- package/dist/runtime.d.ts +53 -0
- package/dist/runtime.js +405 -0
- package/dist/signer.d.ts +27 -0
- package/dist/signer.js +33 -0
- package/dist/state-provider.d.ts +51 -0
- package/dist/state-provider.js +173 -0
- package/package.json +55 -0
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export declare const ACTION_DOMAIN_V1: Uint8Array<ArrayBuffer>;
|
|
2
|
+
export declare const APPLICATION_KEY_CLASS_V1 = 1;
|
|
3
|
+
export declare const RUNTIME_KEY_CLASS_V1 = 0;
|
|
4
|
+
export declare const WALLET_AUTH_MODULE_V1 = 1;
|
|
5
|
+
export declare const MANAGED_STATE_COMMITMENT_KEY_V1: Uint8Array<ArrayBuffer>;
|
|
6
|
+
export declare const MAX_ACTION_PAYLOAD_BYTES = 1048576;
|
|
7
|
+
export declare const OWNERSHIP_VERSION_V1 = 1;
|
|
8
|
+
export declare const MAX_OWNERSHIP_BYTES = 4096;
|
|
9
|
+
export declare const OWNERSHIP_KEY_DOMAIN_V1: Uint8Array<ArrayBuffer>;
|
|
10
|
+
export declare const OWNERSHIP_KIND: {
|
|
11
|
+
readonly ED25519_KEY: 0;
|
|
12
|
+
readonly SR25519_KEY: 1;
|
|
13
|
+
readonly SECP256K1_KEY: 2;
|
|
14
|
+
readonly SECP256K1_KECCAK20: 3;
|
|
15
|
+
readonly MULTICRYPTO_ACCOUNT32: 4;
|
|
16
|
+
};
|
|
17
|
+
export type OwnershipKind = typeof OWNERSHIP_KIND[keyof typeof OWNERSHIP_KIND];
|
|
18
|
+
export type Ownership = {
|
|
19
|
+
version: 1;
|
|
20
|
+
kind: OwnershipKind;
|
|
21
|
+
public: Uint8Array;
|
|
22
|
+
};
|
|
23
|
+
export type ServiceKeyV1 = Uint8Array;
|
|
24
|
+
export type SignedActionV1 = {
|
|
25
|
+
version: 1;
|
|
26
|
+
networkDomain: Uint8Array;
|
|
27
|
+
serviceKey: ServiceKeyV1;
|
|
28
|
+
actionSelector: Uint8Array;
|
|
29
|
+
signerScheme: 0;
|
|
30
|
+
publicKey: Uint8Array;
|
|
31
|
+
nonce: bigint;
|
|
32
|
+
validUntil: bigint;
|
|
33
|
+
payloadHash: Uint8Array;
|
|
34
|
+
signature: Uint8Array;
|
|
35
|
+
payload: Uint8Array;
|
|
36
|
+
};
|
|
37
|
+
export type SignedActionV2 = {
|
|
38
|
+
version: 2;
|
|
39
|
+
networkDomain: Uint8Array;
|
|
40
|
+
serviceKey: ServiceKeyV1;
|
|
41
|
+
actionSelector: Uint8Array;
|
|
42
|
+
controller: Ownership;
|
|
43
|
+
actAs: Ownership | null;
|
|
44
|
+
nonce: bigint;
|
|
45
|
+
validUntil: bigint;
|
|
46
|
+
payloadHash: Uint8Array;
|
|
47
|
+
authorizationProof: Uint8Array;
|
|
48
|
+
payload: Uint8Array;
|
|
49
|
+
};
|
|
50
|
+
export declare function encodeOwnership(ownership: Ownership): Uint8Array;
|
|
51
|
+
export declare function decodeOwnership(bytes: Uint8Array): Ownership;
|
|
52
|
+
export declare function ownershipKey(ownership: Ownership): Uint8Array;
|
|
53
|
+
export declare function ownershipNonceKey(ownership: Ownership): Uint8Array;
|
|
54
|
+
export declare function parseHex(value: string, expectedBytes?: number): Uint8Array;
|
|
55
|
+
export declare function toHex(value: Uint8Array): string;
|
|
56
|
+
export declare function actionSelector(name: string): Uint8Array;
|
|
57
|
+
export declare function stateKey(schema: Uint8Array | string, key: Uint8Array): Uint8Array;
|
|
58
|
+
export declare function nonceKey(publicKey: Uint8Array): Uint8Array;
|
|
59
|
+
export declare function signingDigestV1(action: Omit<SignedActionV1, "signature">): Uint8Array;
|
|
60
|
+
export declare function encodeSignedActionV1(action: SignedActionV1): Uint8Array;
|
|
61
|
+
export declare function decodeSignedActionV1(bytes: Uint8Array): SignedActionV1;
|
|
62
|
+
export declare function actionCommitmentV2(action: Omit<SignedActionV2, "authorizationProof">): Uint8Array;
|
|
63
|
+
export declare function signingMessageV2(action: Omit<SignedActionV2, "authorizationProof">): Uint8Array;
|
|
64
|
+
export declare function encodeSignedActionV2(action: SignedActionV2): Uint8Array;
|
|
65
|
+
export declare function decodeSignedActionV2(bytes: Uint8Array): SignedActionV2;
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { hexToU8a, u8aToHex } from "@polkadot/util";
|
|
2
|
+
import { blake2AsU8a } from "@polkadot/util-crypto";
|
|
3
|
+
export const ACTION_DOMAIN_V1 = new TextEncoder().encode("JAMSCRIPT_ACTION_V1");
|
|
4
|
+
export const APPLICATION_KEY_CLASS_V1 = 0x01;
|
|
5
|
+
export const RUNTIME_KEY_CLASS_V1 = 0x00;
|
|
6
|
+
export const WALLET_AUTH_MODULE_V1 = 0x01;
|
|
7
|
+
export const MANAGED_STATE_COMMITMENT_KEY_V1 = new TextEncoder().encode(":jam-service-runtime:managed-state:v1");
|
|
8
|
+
export const MAX_ACTION_PAYLOAD_BYTES = 1_048_576;
|
|
9
|
+
export const OWNERSHIP_VERSION_V1 = 1;
|
|
10
|
+
export const MAX_OWNERSHIP_BYTES = 4096;
|
|
11
|
+
export const OWNERSHIP_KEY_DOMAIN_V1 = new TextEncoder().encode("OWNERSHIP_ABSTRACTION_KEY_V1");
|
|
12
|
+
export const OWNERSHIP_KIND = {
|
|
13
|
+
ED25519_KEY: 0,
|
|
14
|
+
SR25519_KEY: 1,
|
|
15
|
+
SECP256K1_KEY: 2,
|
|
16
|
+
SECP256K1_KECCAK20: 3,
|
|
17
|
+
MULTICRYPTO_ACCOUNT32: 4,
|
|
18
|
+
};
|
|
19
|
+
function concat(...parts) {
|
|
20
|
+
const output = new Uint8Array(parts.reduce((size, part) => size + part.length, 0));
|
|
21
|
+
let offset = 0;
|
|
22
|
+
for (const part of parts) {
|
|
23
|
+
output.set(part, offset);
|
|
24
|
+
offset += part.length;
|
|
25
|
+
}
|
|
26
|
+
return output;
|
|
27
|
+
}
|
|
28
|
+
function u32(value) {
|
|
29
|
+
const output = new Uint8Array(4);
|
|
30
|
+
new DataView(output.buffer).setUint32(0, value, true);
|
|
31
|
+
return output;
|
|
32
|
+
}
|
|
33
|
+
function u16(value) {
|
|
34
|
+
return Uint8Array.of(value & 0xff, (value >>> 8) & 0xff);
|
|
35
|
+
}
|
|
36
|
+
function u64(value) {
|
|
37
|
+
const output = new Uint8Array(8);
|
|
38
|
+
new DataView(output.buffer).setBigUint64(0, value, true);
|
|
39
|
+
return output;
|
|
40
|
+
}
|
|
41
|
+
function byte(value) {
|
|
42
|
+
return Uint8Array.of(value);
|
|
43
|
+
}
|
|
44
|
+
function ownershipPublicLength(kind) {
|
|
45
|
+
if (kind === OWNERSHIP_KIND.SECP256K1_KEY)
|
|
46
|
+
return 33;
|
|
47
|
+
if (kind === OWNERSHIP_KIND.SECP256K1_KECCAK20)
|
|
48
|
+
return 20;
|
|
49
|
+
return 32;
|
|
50
|
+
}
|
|
51
|
+
export function encodeOwnership(ownership) {
|
|
52
|
+
if (ownership.version !== 1 || ownership.public.length !== ownershipPublicLength(ownership.kind)) {
|
|
53
|
+
throw new Error("invalid Ownership public value");
|
|
54
|
+
}
|
|
55
|
+
if (ownership.kind === OWNERSHIP_KIND.SECP256K1_KEY && ownership.public[0] !== 2 && ownership.public[0] !== 3) {
|
|
56
|
+
throw new Error("invalid compressed secp256k1 public key");
|
|
57
|
+
}
|
|
58
|
+
if (ownership.public.length > 0xffff || ownership.public.length + 4 > MAX_OWNERSHIP_BYTES)
|
|
59
|
+
throw new Error("Ownership is too large");
|
|
60
|
+
return concat(byte(1), byte(ownership.kind), Uint8Array.of(ownership.public.length & 0xff, ownership.public.length >>> 8), ownership.public);
|
|
61
|
+
}
|
|
62
|
+
export function decodeOwnership(bytes) {
|
|
63
|
+
if (bytes.length < 4 || bytes.length > MAX_OWNERSHIP_BYTES || bytes[0] !== 1)
|
|
64
|
+
throw new Error("invalid Ownership encoding");
|
|
65
|
+
const kind = bytes[1];
|
|
66
|
+
if (kind < 0 || kind > 4)
|
|
67
|
+
throw new Error("unsupported Ownership kind");
|
|
68
|
+
const length = bytes[2] | (bytes[3] << 8);
|
|
69
|
+
if (length !== ownershipPublicLength(kind) || bytes.length !== 4 + length)
|
|
70
|
+
throw new Error("invalid Ownership length");
|
|
71
|
+
if (kind === OWNERSHIP_KIND.SECP256K1_KEY && bytes[4] !== 2 && bytes[4] !== 3) {
|
|
72
|
+
throw new Error("invalid compressed secp256k1 public key");
|
|
73
|
+
}
|
|
74
|
+
return { version: 1, kind, public: bytes.slice(4) };
|
|
75
|
+
}
|
|
76
|
+
export function ownershipKey(ownership) {
|
|
77
|
+
return blake2AsU8a(concat(OWNERSHIP_KEY_DOMAIN_V1, encodeOwnership(ownership)), 256);
|
|
78
|
+
}
|
|
79
|
+
export function ownershipNonceKey(ownership) {
|
|
80
|
+
return concat(byte(RUNTIME_KEY_CLASS_V1), byte(WALLET_AUTH_MODULE_V1), new TextEncoder().encode("__jamscript/runtime/ownership/nonces/"), ownershipKey(ownership));
|
|
81
|
+
}
|
|
82
|
+
export function parseHex(value, expectedBytes) {
|
|
83
|
+
const output = hexToU8a(value);
|
|
84
|
+
if (expectedBytes !== undefined && output.length !== expectedBytes) {
|
|
85
|
+
throw new Error("expected " + expectedBytes + " bytes, got " + output.length);
|
|
86
|
+
}
|
|
87
|
+
return output;
|
|
88
|
+
}
|
|
89
|
+
export function toHex(value) {
|
|
90
|
+
return u8aToHex(value);
|
|
91
|
+
}
|
|
92
|
+
export function actionSelector(name) {
|
|
93
|
+
return blake2AsU8a(concat(new TextEncoder().encode("jamscript/action/v1:"), new TextEncoder().encode(name)), 256).slice(0, 8);
|
|
94
|
+
}
|
|
95
|
+
export function stateKey(schema, key) {
|
|
96
|
+
const schemaBytes = typeof schema === "string" ? new TextEncoder().encode(schema) : schema;
|
|
97
|
+
if (schemaBytes.length > 0xffff)
|
|
98
|
+
throw new Error("managed state namespace is too large");
|
|
99
|
+
return concat(byte(APPLICATION_KEY_CLASS_V1), Uint8Array.of(schemaBytes.length & 0xff, schemaBytes.length >>> 8), schemaBytes, key);
|
|
100
|
+
}
|
|
101
|
+
export function nonceKey(publicKey) {
|
|
102
|
+
if (publicKey.length !== 32)
|
|
103
|
+
throw new Error("wallet account must be 32 bytes");
|
|
104
|
+
return concat(byte(RUNTIME_KEY_CLASS_V1), byte(WALLET_AUTH_MODULE_V1), publicKey);
|
|
105
|
+
}
|
|
106
|
+
export function signingDigestV1(action) {
|
|
107
|
+
if (action.networkDomain.length !== 32 || action.serviceKey.length !== 32 || action.actionSelector.length !== 8) {
|
|
108
|
+
throw new Error("invalid SignedActionV1 fixed-width field");
|
|
109
|
+
}
|
|
110
|
+
return blake2AsU8a(concat(ACTION_DOMAIN_V1, byte(action.version), action.networkDomain, action.serviceKey, action.actionSelector, byte(action.signerScheme), u64(action.nonce), u64(action.validUntil), action.payloadHash), 256);
|
|
111
|
+
}
|
|
112
|
+
export function encodeSignedActionV1(action) {
|
|
113
|
+
if (action.networkDomain.length !== 32 || action.serviceKey.length !== 32 || action.actionSelector.length !== 8) {
|
|
114
|
+
throw new Error("invalid SignedActionV1 fixed-width field");
|
|
115
|
+
}
|
|
116
|
+
if (action.publicKey.length > 32 || action.signature.length > 64) {
|
|
117
|
+
throw new Error("SignedActionV1 variable-width field is too large");
|
|
118
|
+
}
|
|
119
|
+
if (action.payload.length > MAX_ACTION_PAYLOAD_BYTES)
|
|
120
|
+
throw new Error("SignedActionV1 payload is too large");
|
|
121
|
+
return concat(byte(action.version), action.networkDomain, action.serviceKey, action.actionSelector, byte(action.signerScheme), byte(action.publicKey.length), action.publicKey, u64(action.nonce), u64(action.validUntil), action.payloadHash, byte(action.signature.length), action.signature, u32(action.payload.length), action.payload);
|
|
122
|
+
}
|
|
123
|
+
export function decodeSignedActionV1(bytes) {
|
|
124
|
+
let offset = 0;
|
|
125
|
+
const take = (length) => {
|
|
126
|
+
const end = offset + length;
|
|
127
|
+
if (end > bytes.length)
|
|
128
|
+
throw new Error("truncated SignedActionV1");
|
|
129
|
+
const value = bytes.slice(offset, end);
|
|
130
|
+
offset = end;
|
|
131
|
+
return value;
|
|
132
|
+
};
|
|
133
|
+
const readU8 = () => take(1)[0];
|
|
134
|
+
const readU32 = () => new DataView(take(4).buffer).getUint32(0, true);
|
|
135
|
+
const readU64 = () => new DataView(take(8).buffer).getBigUint64(0, true);
|
|
136
|
+
const version = readU8();
|
|
137
|
+
const networkDomain = take(32);
|
|
138
|
+
const serviceKey = take(32);
|
|
139
|
+
const actionSelector = take(8);
|
|
140
|
+
const signerScheme = readU8();
|
|
141
|
+
const publicKey = take(readU8());
|
|
142
|
+
const nonce = readU64();
|
|
143
|
+
const validUntil = readU64();
|
|
144
|
+
const payloadHash = take(32);
|
|
145
|
+
const signature = take(readU8());
|
|
146
|
+
const payload = take(readU32());
|
|
147
|
+
if (offset !== bytes.length)
|
|
148
|
+
throw new Error("trailing SignedActionV1 bytes");
|
|
149
|
+
if (version !== 1 || signerScheme !== 0 || publicKey.length !== 32 || signature.length !== 64 || payload.length > MAX_ACTION_PAYLOAD_BYTES) {
|
|
150
|
+
throw new Error("unsupported SignedActionV1");
|
|
151
|
+
}
|
|
152
|
+
if (toHex(blake2AsU8a(payload, 256)) !== toHex(payloadHash)) {
|
|
153
|
+
throw new Error("SignedActionV1 payload hash mismatch");
|
|
154
|
+
}
|
|
155
|
+
return { version: 1, networkDomain, serviceKey, actionSelector, signerScheme: 0, publicKey, nonce, validUntil, payloadHash, signature, payload };
|
|
156
|
+
}
|
|
157
|
+
export function actionCommitmentV2(action) {
|
|
158
|
+
if (action.networkDomain.length !== 32 || action.serviceKey.length !== 32 || action.actionSelector.length !== 8)
|
|
159
|
+
throw new Error("invalid SignedActionV2 fixed-width field");
|
|
160
|
+
const parts = [
|
|
161
|
+
new TextEncoder().encode("JAMSCRIPT_ACTION_V2"),
|
|
162
|
+
action.networkDomain,
|
|
163
|
+
action.serviceKey,
|
|
164
|
+
action.actionSelector,
|
|
165
|
+
encodeOwnership(action.controller),
|
|
166
|
+
byte(action.actAs === null ? 0 : 1),
|
|
167
|
+
...(action.actAs === null ? [] : [encodeOwnership(action.actAs)]),
|
|
168
|
+
u64(action.nonce),
|
|
169
|
+
u64(action.validUntil),
|
|
170
|
+
action.payloadHash,
|
|
171
|
+
];
|
|
172
|
+
return blake2AsU8a(concat(...parts), 256);
|
|
173
|
+
}
|
|
174
|
+
export function signingMessageV2(action) {
|
|
175
|
+
const commitment = actionCommitmentV2(action);
|
|
176
|
+
const encoded = base64Url(commitment);
|
|
177
|
+
return new TextEncoder().encode("JAMSCRIPT_ACTION_V2:" + encoded);
|
|
178
|
+
}
|
|
179
|
+
function base64Url(bytes) {
|
|
180
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
181
|
+
let output = "";
|
|
182
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
183
|
+
const a = bytes[index];
|
|
184
|
+
const b = index + 1 < bytes.length ? bytes[index + 1] : 0;
|
|
185
|
+
const c = index + 2 < bytes.length ? bytes[index + 2] : 0;
|
|
186
|
+
output += alphabet[a >>> 2];
|
|
187
|
+
output += alphabet[((a & 3) << 4) | (b >>> 4)];
|
|
188
|
+
output += index + 1 < bytes.length ? alphabet[((b & 15) << 2) | (c >>> 6)] : "=";
|
|
189
|
+
output += index + 2 < bytes.length ? alphabet[c & 63] : "=";
|
|
190
|
+
}
|
|
191
|
+
return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
192
|
+
}
|
|
193
|
+
export function encodeSignedActionV2(action) {
|
|
194
|
+
if (action.version !== 2 || action.networkDomain.length !== 32 || action.serviceKey.length !== 32 || action.actionSelector.length !== 8)
|
|
195
|
+
throw new Error("invalid SignedActionV2 fixed-width field");
|
|
196
|
+
const controller = encodeOwnership(action.controller);
|
|
197
|
+
const actAs = action.actAs === null ? null : encodeOwnership(action.actAs);
|
|
198
|
+
if (action.authorizationProof.length > 65536)
|
|
199
|
+
throw new Error("SignedActionV2 authorization proof is too large");
|
|
200
|
+
if (action.payload.length > MAX_ACTION_PAYLOAD_BYTES)
|
|
201
|
+
throw new Error("SignedActionV2 payload is too large");
|
|
202
|
+
return concat(byte(2), action.networkDomain, action.serviceKey, action.actionSelector, u16(controller.length), controller, byte(actAs === null ? 0 : 1), ...(actAs === null ? [] : [u16(actAs.length), actAs]), u64(action.nonce), u64(action.validUntil), action.payloadHash, u32(action.authorizationProof.length), action.authorizationProof, u32(action.payload.length), action.payload);
|
|
203
|
+
}
|
|
204
|
+
export function decodeSignedActionV2(bytes) {
|
|
205
|
+
let offset = 0;
|
|
206
|
+
const take = (length) => { const end = offset + length; if (end > bytes.length)
|
|
207
|
+
throw new Error("truncated SignedActionV2"); const value = bytes.slice(offset, end); offset = end; return value; };
|
|
208
|
+
const readU8 = () => take(1)[0];
|
|
209
|
+
const readU16 = () => { const value = take(2); return value[0] | (value[1] << 8); };
|
|
210
|
+
const readU32 = () => new DataView(take(4).buffer).getUint32(0, true);
|
|
211
|
+
const readU64 = () => new DataView(take(8).buffer).getBigUint64(0, true);
|
|
212
|
+
if (readU8() !== 2)
|
|
213
|
+
throw new Error("unsupported SignedActionV2");
|
|
214
|
+
const networkDomain = take(32);
|
|
215
|
+
const serviceKey = take(32);
|
|
216
|
+
const actionSelector = take(8);
|
|
217
|
+
const controller = decodeOwnership(take(readU16()));
|
|
218
|
+
const flag = readU8();
|
|
219
|
+
const actAs = flag === 0 ? null : flag === 1 ? decodeOwnership(take(readU16())) : (() => { throw new Error("invalid SignedActionV2 actAs flag"); })();
|
|
220
|
+
const nonce = readU64();
|
|
221
|
+
const validUntil = readU64();
|
|
222
|
+
const payloadHash = take(32);
|
|
223
|
+
const proofLength = readU32();
|
|
224
|
+
if (proofLength > 65536)
|
|
225
|
+
throw new Error("SignedActionV2 authorization proof is too large");
|
|
226
|
+
const authorizationProof = take(proofLength);
|
|
227
|
+
const payload = take(readU32());
|
|
228
|
+
if (offset !== bytes.length)
|
|
229
|
+
throw new Error("trailing SignedActionV2 bytes");
|
|
230
|
+
if (toHex(blake2AsU8a(payload, 256)) !== toHex(payloadHash))
|
|
231
|
+
throw new Error("SignedActionV2 payload hash mismatch");
|
|
232
|
+
return { version: 2, networkDomain, serviceKey, actionSelector, controller, actAs, nonce, validUntil, payloadHash, authorizationProof, payload };
|
|
233
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./abi.js";
|
|
2
|
+
export * from "./client.js";
|
|
3
|
+
export * from "./codec.js";
|
|
4
|
+
export * from "./crypto.js";
|
|
5
|
+
export * from "./proof.js";
|
|
6
|
+
export * from "./rpc.js";
|
|
7
|
+
export * from "./runtime.js";
|
|
8
|
+
export * from "./signer.js";
|
|
9
|
+
export * from "./state-provider.js";
|
|
10
|
+
export * from "./matrix.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./abi.js";
|
|
2
|
+
export * from "./client.js";
|
|
3
|
+
export * from "./codec.js";
|
|
4
|
+
export * from "./crypto.js";
|
|
5
|
+
export * from "./proof.js";
|
|
6
|
+
export * from "./rpc.js";
|
|
7
|
+
export * from "./runtime.js";
|
|
8
|
+
export * from "./signer.js";
|
|
9
|
+
export * from "./state-provider.js";
|
|
10
|
+
export * from "./matrix.js";
|
package/dist/matrix.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Ownership } from "./crypto.js";
|
|
2
|
+
import type { JamScriptOwnershipSignRequest, OwnershipSigner } from "./signer.js";
|
|
3
|
+
export type MatrixKeyDiscovery = {
|
|
4
|
+
queryMasterPublicKey(userId: string): Promise<Uint8Array>;
|
|
5
|
+
};
|
|
6
|
+
export declare class MatrixOwnershipResolver {
|
|
7
|
+
private readonly discovery;
|
|
8
|
+
constructor(discovery: MatrixKeyDiscovery);
|
|
9
|
+
resolve(userId: string): Promise<Ownership>;
|
|
10
|
+
}
|
|
11
|
+
export type MatrixDeviceSigning = {
|
|
12
|
+
sign(message: Uint8Array): Promise<Uint8Array>;
|
|
13
|
+
};
|
|
14
|
+
export declare class MatrixDeviceController implements OwnershipSigner {
|
|
15
|
+
private readonly signing;
|
|
16
|
+
private readonly ownership;
|
|
17
|
+
constructor(deviceEd25519PublicKey: Uint8Array, signing: MatrixDeviceSigning);
|
|
18
|
+
getController(): Promise<Ownership>;
|
|
19
|
+
signJamScriptAction(request: JamScriptOwnershipSignRequest): Promise<Uint8Array>;
|
|
20
|
+
}
|
|
21
|
+
export type MatrixControlClaimBootstrapper = {
|
|
22
|
+
bootstrap(subject: Ownership, controller: Ownership, proof: Uint8Array): Promise<void>;
|
|
23
|
+
};
|
package/dist/matrix.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { OWNERSHIP_KIND } from "./crypto.js";
|
|
2
|
+
export class MatrixOwnershipResolver {
|
|
3
|
+
discovery;
|
|
4
|
+
constructor(discovery) {
|
|
5
|
+
this.discovery = discovery;
|
|
6
|
+
}
|
|
7
|
+
async resolve(userId) {
|
|
8
|
+
if (!/^@[^\s:]+:[^\s:]+$/.test(userId))
|
|
9
|
+
throw new Error("invalid Matrix User ID");
|
|
10
|
+
const publicKey = await this.discovery.queryMasterPublicKey(userId);
|
|
11
|
+
if (publicKey.length !== 32)
|
|
12
|
+
throw new Error("Matrix master key must be 32 bytes");
|
|
13
|
+
return { version: 1, kind: OWNERSHIP_KIND.ED25519_KEY, public: publicKey };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class MatrixDeviceController {
|
|
17
|
+
signing;
|
|
18
|
+
ownership;
|
|
19
|
+
constructor(deviceEd25519PublicKey, signing) {
|
|
20
|
+
this.signing = signing;
|
|
21
|
+
if (deviceEd25519PublicKey.length !== 32)
|
|
22
|
+
throw new Error("Matrix device key must be 32 bytes");
|
|
23
|
+
this.ownership = { version: 1, kind: OWNERSHIP_KIND.ED25519_KEY, public: deviceEd25519PublicKey };
|
|
24
|
+
}
|
|
25
|
+
async getController() {
|
|
26
|
+
return this.ownership;
|
|
27
|
+
}
|
|
28
|
+
signJamScriptAction(request) {
|
|
29
|
+
return this.signing.sign(request.message);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/proof.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function verifyManagedStateProof(stateRoot: Uint8Array, key: Uint8Array, claimedValue: Uint8Array | null, proof: Uint8Array[]): Uint8Array | null;
|
package/dist/proof.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { trieNodeDec, validateProofs, } from "@polkadot-api/substrate-bindings";
|
|
2
|
+
export function verifyManagedStateProof(stateRoot, key, claimedValue, proof) {
|
|
3
|
+
if (stateRoot.length !== 32)
|
|
4
|
+
throw new Error("managed-state root must be 32 bytes");
|
|
5
|
+
if (proof.length === 0)
|
|
6
|
+
throw new Error("managed-state proof is empty");
|
|
7
|
+
const validated = validateProofs(proof);
|
|
8
|
+
if (validated === null || normalizeHex(validated.rootHash) !== bytesToHex(stateRoot)) {
|
|
9
|
+
throw new Error("managed-state proof does not match the requested root");
|
|
10
|
+
}
|
|
11
|
+
const keyNibbles = bytesToHex(key).slice(2);
|
|
12
|
+
let offset = 0;
|
|
13
|
+
let node = requireProofNode(validated.proofs, validated.rootHash);
|
|
14
|
+
for (;;) {
|
|
15
|
+
if (node.type === "Raw" || node.type === "Empty" || node.type === "Reserved") {
|
|
16
|
+
if (node.type === "Empty")
|
|
17
|
+
return assertClaimedValue(null, claimedValue);
|
|
18
|
+
throw new Error("invalid managed-state proof node");
|
|
19
|
+
}
|
|
20
|
+
if (!keyNibbles.startsWith(node.partialKey, offset)) {
|
|
21
|
+
return assertClaimedValue(null, claimedValue);
|
|
22
|
+
}
|
|
23
|
+
offset += node.partialKey.length;
|
|
24
|
+
if (node.type === "Leaf" || node.type === "LeafWithHash") {
|
|
25
|
+
const value = offset === keyNibbles.length
|
|
26
|
+
? resolveValue(node.type, node.value, validated.proofs)
|
|
27
|
+
: null;
|
|
28
|
+
return assertClaimedValue(value, claimedValue);
|
|
29
|
+
}
|
|
30
|
+
if (offset === keyNibbles.length) {
|
|
31
|
+
const value = node.type === "BranchWithVal"
|
|
32
|
+
? hexToBytes(node.value)
|
|
33
|
+
: node.type === "BranchWithHash"
|
|
34
|
+
? resolveHashedValue(node.value, validated.proofs)
|
|
35
|
+
: null;
|
|
36
|
+
return assertClaimedValue(value, claimedValue);
|
|
37
|
+
}
|
|
38
|
+
if (!("children" in node))
|
|
39
|
+
throw new Error("invalid managed-state branch proof");
|
|
40
|
+
const child = node.children[keyNibbles[offset]];
|
|
41
|
+
offset += 1;
|
|
42
|
+
if (child === undefined)
|
|
43
|
+
return assertClaimedValue(null, claimedValue);
|
|
44
|
+
const childBytes = hexToBytes(child);
|
|
45
|
+
node = childBytes.length === 32
|
|
46
|
+
? requireProofNode(validated.proofs, child)
|
|
47
|
+
: trieNodeDec(childBytes);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function resolveValue(type, value, proofs) {
|
|
51
|
+
return type === "Leaf" ? decodeCompactBytes(hexToBytes(value)) : resolveHashedValue(value, proofs);
|
|
52
|
+
}
|
|
53
|
+
function resolveHashedValue(valueHash, proofs) {
|
|
54
|
+
const node = requireProofNode(proofs, valueHash);
|
|
55
|
+
if (node.type !== "Raw")
|
|
56
|
+
throw new Error("managed-state proof is missing a hashed value");
|
|
57
|
+
return hexToBytes(node.value);
|
|
58
|
+
}
|
|
59
|
+
function requireProofNode(proofs, hash) {
|
|
60
|
+
const node = proofs[normalizeHex(hash)];
|
|
61
|
+
if (node === undefined)
|
|
62
|
+
throw new Error("managed-state proof is incomplete");
|
|
63
|
+
return node;
|
|
64
|
+
}
|
|
65
|
+
function assertClaimedValue(verified, claimed) {
|
|
66
|
+
if (verified === null || claimed === null) {
|
|
67
|
+
if (verified !== claimed)
|
|
68
|
+
throw new Error("managed-state value does not match its proof");
|
|
69
|
+
return verified;
|
|
70
|
+
}
|
|
71
|
+
if (verified.length !== claimed.length || verified.some((byte, index) => byte !== claimed[index])) {
|
|
72
|
+
throw new Error("managed-state value does not match its proof");
|
|
73
|
+
}
|
|
74
|
+
return verified;
|
|
75
|
+
}
|
|
76
|
+
function normalizeHex(value) {
|
|
77
|
+
return "0x" + value.toLowerCase().replace(/^0x/, "");
|
|
78
|
+
}
|
|
79
|
+
function bytesToHex(value) {
|
|
80
|
+
return "0x" + Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
81
|
+
}
|
|
82
|
+
function hexToBytes(value) {
|
|
83
|
+
const hex = normalizeHex(value).slice(2);
|
|
84
|
+
if (hex.length % 2 !== 0 || !/^[0-9a-f]*$/.test(hex))
|
|
85
|
+
throw new Error("invalid proof hex");
|
|
86
|
+
return Uint8Array.from(hex.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) ?? []);
|
|
87
|
+
}
|
|
88
|
+
function decodeCompactBytes(encoded) {
|
|
89
|
+
if (encoded.length === 0)
|
|
90
|
+
throw new Error("invalid compact proof value");
|
|
91
|
+
const mode = encoded[0] & 3;
|
|
92
|
+
let length;
|
|
93
|
+
let prefixLength;
|
|
94
|
+
if (mode === 0) {
|
|
95
|
+
length = encoded[0] >>> 2;
|
|
96
|
+
prefixLength = 1;
|
|
97
|
+
}
|
|
98
|
+
else if (mode === 1) {
|
|
99
|
+
if (encoded.length < 2)
|
|
100
|
+
throw new Error("invalid compact proof value");
|
|
101
|
+
length = ((encoded[0] | (encoded[1] << 8)) >>> 2);
|
|
102
|
+
prefixLength = 2;
|
|
103
|
+
}
|
|
104
|
+
else if (mode === 2) {
|
|
105
|
+
if (encoded.length < 4)
|
|
106
|
+
throw new Error("invalid compact proof value");
|
|
107
|
+
length = new DataView(encoded.buffer, encoded.byteOffset, 4).getUint32(0, true) >>> 2;
|
|
108
|
+
prefixLength = 4;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const lengthBytes = (encoded[0] >>> 2) + 4;
|
|
112
|
+
if (lengthBytes > 6 || encoded.length < 1 + lengthBytes) {
|
|
113
|
+
throw new Error("compact proof value is too large");
|
|
114
|
+
}
|
|
115
|
+
length = 0;
|
|
116
|
+
for (let index = 0; index < lengthBytes; index += 1) {
|
|
117
|
+
length += encoded[1 + index] * 2 ** (8 * index);
|
|
118
|
+
}
|
|
119
|
+
prefixLength = 1 + lengthBytes;
|
|
120
|
+
}
|
|
121
|
+
if (!Number.isSafeInteger(length) || prefixLength + length !== encoded.length) {
|
|
122
|
+
throw new Error("invalid compact proof value length");
|
|
123
|
+
}
|
|
124
|
+
return encoded.slice(prefixLength);
|
|
125
|
+
}
|
package/dist/rpc.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export type FinalizedContext = {
|
|
2
|
+
blockHash: string;
|
|
3
|
+
blockNumber: number;
|
|
4
|
+
stateRoot: string;
|
|
5
|
+
slot: number;
|
|
6
|
+
};
|
|
7
|
+
export type SubmitWorkRequest = {
|
|
8
|
+
context: {
|
|
9
|
+
blockHash: string;
|
|
10
|
+
stateRoot: string;
|
|
11
|
+
slot: number;
|
|
12
|
+
};
|
|
13
|
+
serviceId: number;
|
|
14
|
+
serviceCodeHash: string;
|
|
15
|
+
payloadBase64: string;
|
|
16
|
+
extrinsicsBase64: string[];
|
|
17
|
+
};
|
|
18
|
+
export type SubmitWorkResult = {
|
|
19
|
+
packageHash: string;
|
|
20
|
+
submissionHash: string;
|
|
21
|
+
context: FinalizedContext;
|
|
22
|
+
/** Hash of the exact SignedActionV1 bytes submitted by submitAction. */
|
|
23
|
+
actionHash?: string;
|
|
24
|
+
};
|
|
25
|
+
export type SubmitTransactionRequest = {
|
|
26
|
+
serviceId: number;
|
|
27
|
+
serviceCodeHash: string;
|
|
28
|
+
payloadBase64: string;
|
|
29
|
+
extrinsicsBase64: string[];
|
|
30
|
+
};
|
|
31
|
+
export type TransactionState = "queued" | "packaged" | "refining" | "reported" | "imported" | "failed";
|
|
32
|
+
export type SubmitTransactionResult = {
|
|
33
|
+
transactionId: string;
|
|
34
|
+
status: TransactionState;
|
|
35
|
+
packageHash?: string | null;
|
|
36
|
+
itemIndex?: number | null;
|
|
37
|
+
actionIndex?: number | null;
|
|
38
|
+
};
|
|
39
|
+
export type SubmitActionResult = SubmitTransactionResult & {
|
|
40
|
+
actionHash: string;
|
|
41
|
+
};
|
|
42
|
+
export type TransactionStatusResult = {
|
|
43
|
+
transactionId: string;
|
|
44
|
+
status: TransactionState;
|
|
45
|
+
packageHash: string | null;
|
|
46
|
+
itemIndex: number | null;
|
|
47
|
+
actionIndex: number | null;
|
|
48
|
+
executionReceipt: string | null;
|
|
49
|
+
error: string | null;
|
|
50
|
+
actionReceipts?: ActionReceipt[];
|
|
51
|
+
};
|
|
52
|
+
export type WorkStatus = "insufficient_workers" | "awaiting_candidate" | "voting" | "accepted" | "imported" | "failed";
|
|
53
|
+
export type WorkStatusResult = {
|
|
54
|
+
packageHash: string;
|
|
55
|
+
workId: number | null;
|
|
56
|
+
status: WorkStatus;
|
|
57
|
+
executionReceipt: string | null;
|
|
58
|
+
actionReceipts?: ActionReceipt[];
|
|
59
|
+
context: FinalizedContext;
|
|
60
|
+
};
|
|
61
|
+
export type ActionReceipt = {
|
|
62
|
+
actionHash: string;
|
|
63
|
+
status: "applied" | "failed" | "rejected";
|
|
64
|
+
errorCode: number | null;
|
|
65
|
+
};
|
|
66
|
+
export type ManagedStateResult = {
|
|
67
|
+
serviceId: number;
|
|
68
|
+
stateRoot?: string;
|
|
69
|
+
managedStateRoot?: string;
|
|
70
|
+
serviceKey?: string;
|
|
71
|
+
keyBase64: string;
|
|
72
|
+
valueBase64: string | null;
|
|
73
|
+
proofBase64?: string[];
|
|
74
|
+
finalizedContext?: FinalizedContext;
|
|
75
|
+
};
|
|
76
|
+
export type BackendCapabilitiesV1 = {
|
|
77
|
+
protocolVersion: number;
|
|
78
|
+
managedStateVersion: number;
|
|
79
|
+
multiService: boolean;
|
|
80
|
+
externalStateWitness: boolean;
|
|
81
|
+
dynamicPvmServices: boolean;
|
|
82
|
+
};
|
|
83
|
+
export declare class RpcError extends Error {
|
|
84
|
+
readonly code: number;
|
|
85
|
+
readonly data?: unknown | undefined;
|
|
86
|
+
constructor(message: string, code: number, data?: unknown | undefined);
|
|
87
|
+
}
|
|
88
|
+
export interface RpcTransport {
|
|
89
|
+
call<T>(method: string, params?: unknown): Promise<T>;
|
|
90
|
+
}
|
|
91
|
+
export declare class SplitRpcTransport implements RpcTransport {
|
|
92
|
+
private readonly node;
|
|
93
|
+
private readonly work;
|
|
94
|
+
private readonly state;
|
|
95
|
+
constructor(node: RpcTransport, work: RpcTransport, state?: RpcTransport);
|
|
96
|
+
call<T>(method: string, params?: unknown): Promise<T>;
|
|
97
|
+
}
|
|
98
|
+
export declare class FetchRpcTransport implements RpcTransport {
|
|
99
|
+
private readonly endpoint;
|
|
100
|
+
private readonly fetchImpl;
|
|
101
|
+
private nextId;
|
|
102
|
+
constructor(endpoint: string, fetchImpl?: typeof fetch);
|
|
103
|
+
call<T>(method: string, params?: unknown): Promise<T>;
|
|
104
|
+
}
|
|
105
|
+
export type WorkRpc = RpcTransport & {
|
|
106
|
+
finalizedContext(): Promise<FinalizedContext>;
|
|
107
|
+
genesisHash(): Promise<string>;
|
|
108
|
+
serviceStorageAt(blockHash: string, serviceId: number, key: string): Promise<string | null>;
|
|
109
|
+
managedStateAt(serviceId: number, stateRoot: string, keyBase64: string): Promise<ManagedStateResult>;
|
|
110
|
+
submitWork(request: SubmitWorkRequest): Promise<SubmitWorkResult>;
|
|
111
|
+
workStatus(packageHash: string, serviceId?: number): Promise<WorkStatusResult>;
|
|
112
|
+
submitTransaction(request: SubmitTransactionRequest): Promise<SubmitTransactionResult>;
|
|
113
|
+
transactionStatus(transactionId: string): Promise<TransactionStatusResult>;
|
|
114
|
+
};
|
|
115
|
+
export declare function asWorkRpc(transport: RpcTransport): WorkRpc;
|