@calimero-network/mero-js 14.0.0 → 15.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.
- package/dist/crypto/internal.d.ts +30 -0
- package/dist/crypto/internal.js +93 -0
- package/dist/device-cert/device-cert.d.ts +68 -0
- package/dist/device-cert/device-cert.js +110 -0
- package/dist/device-cert/index.d.ts +2 -0
- package/dist/device-cert/index.js +2 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +105 -116
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -2
- package/dist/index.mjs +105 -116
- package/dist/index.mjs.map +4 -4
- package/dist/warrant/warrant.d.ts +1 -0
- package/dist/warrant/warrant.js +1 -79
- package/package.json +1 -1
- package/dist/account/index.d.ts +0 -43
- package/dist/account/index.js +0 -139
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* this producing well-formed warrants whose signatures verify nowhere, surfacing
|
|
22
22
|
* at a relay as a 403: an authorization refusal, nowhere near the cause.
|
|
23
23
|
*/
|
|
24
|
+
/** Ed25519 PKCS#8 prefix, so a raw 32-byte seed can be imported by WebCrypto. */
|
|
24
25
|
/** What a warrant authorises. */
|
|
25
26
|
export interface WarrantInput {
|
|
26
27
|
/**
|
package/dist/warrant/warrant.js
CHANGED
|
@@ -21,56 +21,9 @@
|
|
|
21
21
|
* this producing well-formed warrants whose signatures verify nowhere, surfacing
|
|
22
22
|
* at a relay as a 403: an authorization refusal, nowhere near the cause.
|
|
23
23
|
*/
|
|
24
|
+
import { concat, derivePublicKey, domainHash, fromHex, hex, importSigningKey, u64le, } from '../crypto/internal.js';
|
|
24
25
|
const SIGN_DOMAIN = new TextEncoder().encode('calimero.warrant.v1');
|
|
25
26
|
const INTENT_DOMAIN = new TextEncoder().encode('calimero.warrant.intent.v1');
|
|
26
|
-
/** Ed25519 PKCS#8 prefix, so a raw 32-byte seed can be imported by WebCrypto. */
|
|
27
|
-
const PKCS8_ED25519_PREFIX = new Uint8Array([
|
|
28
|
-
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04,
|
|
29
|
-
0x22, 0x04, 0x20,
|
|
30
|
-
]);
|
|
31
|
-
function hex(bytes) {
|
|
32
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
33
|
-
}
|
|
34
|
-
function fromHex(value, label, expectedBytes) {
|
|
35
|
-
const clean = value.trim();
|
|
36
|
-
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
|
|
37
|
-
throw new Error(`${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`);
|
|
38
|
-
}
|
|
39
|
-
// Indexed rather than `match(/../g)!`: the length is already validated above,
|
|
40
|
-
// so the assertion would only be telling the compiler what the guard proved,
|
|
41
|
-
// and this needs no assertion at all.
|
|
42
|
-
const bytes = new Uint8Array(expectedBytes);
|
|
43
|
-
for (let i = 0; i < expectedBytes; i += 1) {
|
|
44
|
-
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
45
|
-
}
|
|
46
|
-
return bytes;
|
|
47
|
-
}
|
|
48
|
-
function u64le(value) {
|
|
49
|
-
const out = new Uint8Array(8);
|
|
50
|
-
new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
function concat(...parts) {
|
|
54
|
-
const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
|
|
55
|
-
let offset = 0;
|
|
56
|
-
for (const part of parts) {
|
|
57
|
-
out.set(part, offset);
|
|
58
|
-
offset += part.length;
|
|
59
|
-
}
|
|
60
|
-
return out;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* core's `domain_hash`: SHA-256 over the length-prefixed domain, then each
|
|
64
|
-
* length-prefixed part. The lengths are what stop two different field splits from
|
|
65
|
-
* hashing alike.
|
|
66
|
-
*/
|
|
67
|
-
async function domainHash(domain, parts) {
|
|
68
|
-
const chunks = [u64le(domain.length), domain];
|
|
69
|
-
for (const part of parts) {
|
|
70
|
-
chunks.push(u64le(part.length), part);
|
|
71
|
-
}
|
|
72
|
-
return new Uint8Array(await crypto.subtle.digest('SHA-256', concat(...chunks)));
|
|
73
|
-
}
|
|
74
27
|
/**
|
|
75
28
|
* The commitment a warrant carries in place of the intent itself.
|
|
76
29
|
*
|
|
@@ -81,22 +34,6 @@ export async function intentHash(method, argsJson) {
|
|
|
81
34
|
const args = new TextEncoder().encode(JSON.stringify(argsJson));
|
|
82
35
|
return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
|
|
83
36
|
}
|
|
84
|
-
async function importSigningKey(secretHex) {
|
|
85
|
-
const seed = fromHex(secretHex, 'deviceSecret', 32);
|
|
86
|
-
const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
|
|
87
|
-
try {
|
|
88
|
-
return await crypto.subtle.importKey('pkcs8', pkcs8, { name: 'Ed25519' }, false, ['sign']);
|
|
89
|
-
}
|
|
90
|
-
catch (cause) {
|
|
91
|
-
// The original message is folded in rather than passed as `cause`: this
|
|
92
|
-
// package targets ES2020, where `Error` has no `cause` option.
|
|
93
|
-
const detail = cause instanceof Error ? `: ${cause.message}` : '';
|
|
94
|
-
throw new Error('this runtime has no WebCrypto Ed25519, so a warrant cannot be signed ' +
|
|
95
|
-
'here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; ' +
|
|
96
|
-
'otherwise mint the warrant with `merod account warrant`' +
|
|
97
|
-
detail);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
37
|
/**
|
|
101
38
|
* Sign a warrant and return it hex-encoded, ready for `performIntent`.
|
|
102
39
|
*
|
|
@@ -126,18 +63,3 @@ export async function signWarrant(input) {
|
|
|
126
63
|
const signature = new Uint8Array(await crypto.subtle.sign({ name: 'Ed25519' }, key, preimage));
|
|
127
64
|
return hex(concat(context, authorAccount, publicKey, executor, commitment, nonce, notAfter, signature));
|
|
128
65
|
}
|
|
129
|
-
/**
|
|
130
|
-
* The public half of an ed25519 seed.
|
|
131
|
-
*
|
|
132
|
-
* Derived rather than taken as an argument, for the reason core does the same: a
|
|
133
|
-
* caller able to name a key it does not hold could produce a warrant it cannot
|
|
134
|
-
* sign, and the field would stop meaning "who authorised this".
|
|
135
|
-
*/
|
|
136
|
-
async function derivePublicKey(secretHex) {
|
|
137
|
-
const seed = fromHex(secretHex, 'deviceSecret', 32);
|
|
138
|
-
const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
|
|
139
|
-
const key = await crypto.subtle.importKey('pkcs8', pkcs8, { name: 'Ed25519' }, true, ['sign']);
|
|
140
|
-
const jwk = await crypto.subtle.exportKey('jwk', key);
|
|
141
|
-
const raw = jwk.x;
|
|
142
|
-
return Uint8Array.from(atob(raw.replace(/-/g, '+').replace(/_/g, '/')), (c) => c.charCodeAt(0));
|
|
143
|
-
}
|
package/package.json
CHANGED
package/dist/account/index.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Account id encoding.
|
|
3
|
-
*
|
|
4
|
-
* **Core is hex everywhere now** — the admin API and every other id it spells.
|
|
5
|
-
* This module used to describe a deliberate split, hex on the admin API against
|
|
6
|
-
* base58 in contract data, and the core half of that is gone.
|
|
7
|
-
*
|
|
8
|
-
* The app half can remain. `env::account_id()` hands a guest 32 **raw bytes**,
|
|
9
|
-
* not a string, so whatever an app stores in `sender` or keys `get_profiles` by
|
|
10
|
-
* is the app's own rendering. An app that base58-encodes those bytes still
|
|
11
|
-
* produces contract data that a hex admin id will not `===` — and for that app,
|
|
12
|
-
* these helpers are still the boundary.
|
|
13
|
-
*
|
|
14
|
-
* So they stay, with their reason corrected: the mismatch they bridge is an
|
|
15
|
-
* application convention, not something core imposes. An app that hex-encodes
|
|
16
|
-
* needs none of this, and `toAccountHex` is a no-op on what it already holds.
|
|
17
|
-
*
|
|
18
|
-
* They convert accounts and nothing else: a device key, an alias or any other
|
|
19
|
-
* value is returned unchanged, because converting is this module's job and
|
|
20
|
-
* destroying what it cannot convert is not.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* Canonicalise an account id to hex — the form every admin route expects.
|
|
24
|
-
*
|
|
25
|
-
* Returns the input unchanged if it is not a 32-byte account, so a device key
|
|
26
|
-
* or an alias survives a call to this function intact.
|
|
27
|
-
*/
|
|
28
|
-
export declare function toAccountHex(id: string | null | undefined): string;
|
|
29
|
-
/**
|
|
30
|
-
* Canonicalise an account id to base58 — the form the contract stamps on
|
|
31
|
-
* `sender` and keys `get_profiles` by.
|
|
32
|
-
*
|
|
33
|
-
* Returns the input unchanged if it is not a 32-byte account.
|
|
34
|
-
*/
|
|
35
|
-
export declare function toAccountBase58(id: string | null | undefined): string;
|
|
36
|
-
/**
|
|
37
|
-
* True when both ids name the same account, whichever encoding each is in.
|
|
38
|
-
*
|
|
39
|
-
* This is the comparison to reach for when checking a contract-sourced
|
|
40
|
-
* `sender` against an admin-sourced member id. Two ids that are not accounts
|
|
41
|
-
* compare false rather than accidentally matching as raw strings.
|
|
42
|
-
*/
|
|
43
|
-
export declare function sameAccount(a: string | null | undefined, b: string | null | undefined): boolean;
|
package/dist/account/index.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Account id encoding.
|
|
3
|
-
*
|
|
4
|
-
* **Core is hex everywhere now** — the admin API and every other id it spells.
|
|
5
|
-
* This module used to describe a deliberate split, hex on the admin API against
|
|
6
|
-
* base58 in contract data, and the core half of that is gone.
|
|
7
|
-
*
|
|
8
|
-
* The app half can remain. `env::account_id()` hands a guest 32 **raw bytes**,
|
|
9
|
-
* not a string, so whatever an app stores in `sender` or keys `get_profiles` by
|
|
10
|
-
* is the app's own rendering. An app that base58-encodes those bytes still
|
|
11
|
-
* produces contract data that a hex admin id will not `===` — and for that app,
|
|
12
|
-
* these helpers are still the boundary.
|
|
13
|
-
*
|
|
14
|
-
* So they stay, with their reason corrected: the mismatch they bridge is an
|
|
15
|
-
* application convention, not something core imposes. An app that hex-encodes
|
|
16
|
-
* needs none of this, and `toAccountHex` is a no-op on what it already holds.
|
|
17
|
-
*
|
|
18
|
-
* They convert accounts and nothing else: a device key, an alias or any other
|
|
19
|
-
* value is returned unchanged, because converting is this module's job and
|
|
20
|
-
* destroying what it cannot convert is not.
|
|
21
|
-
*/
|
|
22
|
-
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
23
|
-
/** A 32-byte account rendered as 64 hex characters. */
|
|
24
|
-
const HEX_ACCOUNT = /^[0-9a-f]{64}$/i;
|
|
25
|
-
/** Bytes → base58. Leading zero bytes become leading '1's, as in bitcoin base58. */
|
|
26
|
-
function encodeBase58(bytes) {
|
|
27
|
-
if (bytes.length === 0)
|
|
28
|
-
return '';
|
|
29
|
-
const digits = [0];
|
|
30
|
-
for (const byte of bytes) {
|
|
31
|
-
let carry = byte;
|
|
32
|
-
for (let i = 0; i < digits.length; i++) {
|
|
33
|
-
carry += digits[i] << 8;
|
|
34
|
-
digits[i] = carry % 58;
|
|
35
|
-
carry = (carry / 58) | 0;
|
|
36
|
-
}
|
|
37
|
-
while (carry > 0) {
|
|
38
|
-
digits.push(carry % 58);
|
|
39
|
-
carry = (carry / 58) | 0;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
let out = '';
|
|
43
|
-
for (const byte of bytes) {
|
|
44
|
-
if (byte !== 0)
|
|
45
|
-
break;
|
|
46
|
-
out += BASE58_ALPHABET[0];
|
|
47
|
-
}
|
|
48
|
-
for (let i = digits.length - 1; i >= 0; i--) {
|
|
49
|
-
out += BASE58_ALPHABET[digits[i]];
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
/** base58 → bytes. Returns null when the input is not valid base58. */
|
|
54
|
-
function decodeBase58(value) {
|
|
55
|
-
if (value.length === 0)
|
|
56
|
-
return null;
|
|
57
|
-
const bytes = [0];
|
|
58
|
-
for (const char of value) {
|
|
59
|
-
const index = BASE58_ALPHABET.indexOf(char);
|
|
60
|
-
if (index < 0)
|
|
61
|
-
return null;
|
|
62
|
-
let carry = index;
|
|
63
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
64
|
-
carry += bytes[i] * 58;
|
|
65
|
-
bytes[i] = carry & 0xff;
|
|
66
|
-
carry >>= 8;
|
|
67
|
-
}
|
|
68
|
-
while (carry > 0) {
|
|
69
|
-
bytes.push(carry & 0xff);
|
|
70
|
-
carry >>= 8;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
let leadingZeros = 0;
|
|
74
|
-
for (const char of value) {
|
|
75
|
-
if (char !== BASE58_ALPHABET[0])
|
|
76
|
-
break;
|
|
77
|
-
leadingZeros++;
|
|
78
|
-
}
|
|
79
|
-
return Uint8Array.from([
|
|
80
|
-
...new Array(leadingZeros).fill(0),
|
|
81
|
-
...bytes.reverse(),
|
|
82
|
-
]);
|
|
83
|
-
}
|
|
84
|
-
function toHex(bytes) {
|
|
85
|
-
let out = '';
|
|
86
|
-
for (const byte of bytes)
|
|
87
|
-
out += byte.toString(16).padStart(2, '0');
|
|
88
|
-
return out;
|
|
89
|
-
}
|
|
90
|
-
function fromHex(hex) {
|
|
91
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
92
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
93
|
-
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
94
|
-
}
|
|
95
|
-
return bytes;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Canonicalise an account id to hex — the form every admin route expects.
|
|
99
|
-
*
|
|
100
|
-
* Returns the input unchanged if it is not a 32-byte account, so a device key
|
|
101
|
-
* or an alias survives a call to this function intact.
|
|
102
|
-
*/
|
|
103
|
-
export function toAccountHex(id) {
|
|
104
|
-
const trimmed = (id ?? '').trim();
|
|
105
|
-
if (!trimmed)
|
|
106
|
-
return '';
|
|
107
|
-
if (HEX_ACCOUNT.test(trimmed))
|
|
108
|
-
return trimmed.toLowerCase();
|
|
109
|
-
const decoded = decodeBase58(trimmed);
|
|
110
|
-
if (!decoded || decoded.length !== 32)
|
|
111
|
-
return trimmed;
|
|
112
|
-
return toHex(decoded);
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Canonicalise an account id to base58 — the form the contract stamps on
|
|
116
|
-
* `sender` and keys `get_profiles` by.
|
|
117
|
-
*
|
|
118
|
-
* Returns the input unchanged if it is not a 32-byte account.
|
|
119
|
-
*/
|
|
120
|
-
export function toAccountBase58(id) {
|
|
121
|
-
const trimmed = (id ?? '').trim();
|
|
122
|
-
if (!trimmed)
|
|
123
|
-
return '';
|
|
124
|
-
if (!HEX_ACCOUNT.test(trimmed))
|
|
125
|
-
return trimmed;
|
|
126
|
-
return encodeBase58(fromHex(trimmed));
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* True when both ids name the same account, whichever encoding each is in.
|
|
130
|
-
*
|
|
131
|
-
* This is the comparison to reach for when checking a contract-sourced
|
|
132
|
-
* `sender` against an admin-sourced member id. Two ids that are not accounts
|
|
133
|
-
* compare false rather than accidentally matching as raw strings.
|
|
134
|
-
*/
|
|
135
|
-
export function sameAccount(a, b) {
|
|
136
|
-
const left = toAccountHex(a);
|
|
137
|
-
const right = toAccountHex(b);
|
|
138
|
-
return HEX_ACCOUNT.test(left) && left === right;
|
|
139
|
-
}
|