hd-wallet-wasm 2.0.19 → 2.0.20
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/package.json +1 -1
- package/src/epm-attestation.mjs +146 -24
- package/src/index.mjs +1 -1
package/package.json
CHANGED
package/src/epm-attestation.mjs
CHANGED
|
@@ -59,34 +59,156 @@ export function buildCanonicalPayload({
|
|
|
59
59
|
// EPM Content Signing
|
|
60
60
|
// =============================================================================
|
|
61
61
|
|
|
62
|
+
// EntityType / KeyType enum labels (FlatBuffer order). The in-module verifier
|
|
63
|
+
// emits these names, so we must too.
|
|
64
|
+
const EPM_ENTITY_TYPE_NAMES = ['User', 'Node'];
|
|
65
|
+
const EPM_KEY_TYPE_NAMES = ['Signing', 'Encryption'];
|
|
66
|
+
|
|
67
|
+
// Whitespace set trimmed by the Go/C++ canonicalizer: space, tab, NL, CR, VT, FF.
|
|
68
|
+
// (Deliberately NOT JS \s, which also strips NBSP/U+2028/etc. and would diverge.)
|
|
69
|
+
function epmTrim(value) {
|
|
70
|
+
if (typeof value !== 'string') return '';
|
|
71
|
+
return value.replace(/^[ \t\n\r\x0b\f]+/, '').replace(/[ \t\n\r\x0b\f]+$/, '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Go addBytesString: trim, omit when empty.
|
|
75
|
+
function epmAddStr(obj, key, value) {
|
|
76
|
+
const t = epmTrim(value);
|
|
77
|
+
if (t !== '') obj[key] = t;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Trimmed non-empty strings -> array; attach only when non-empty.
|
|
81
|
+
function epmAddStrArray(obj, key, values) {
|
|
82
|
+
if (!Array.isArray(values)) return;
|
|
83
|
+
const arr = [];
|
|
84
|
+
for (const v of values) {
|
|
85
|
+
const t = epmTrim(v);
|
|
86
|
+
if (t !== '') arr.push(t);
|
|
87
|
+
}
|
|
88
|
+
if (arr.length) obj[key] = arr;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function epmEnumName(value, names) {
|
|
92
|
+
if (typeof value === 'number') return names[value];
|
|
93
|
+
return value; // already a label, or undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* RFC 8785 (JCS) canonicalization: recursively sort object keys by UTF-16 code
|
|
98
|
+
* units, then ECMAScript JSON.stringify (minimal escaping, no HTML escaping of
|
|
99
|
+
* & < >, raw non-ASCII, integer numbers). Byte-identical to common/jcs in wasm.
|
|
100
|
+
*/
|
|
101
|
+
function epmJcsCanonicalize(value) {
|
|
102
|
+
const sortDeep = (v) => {
|
|
103
|
+
if (Array.isArray(v)) return v.map(sortDeep);
|
|
104
|
+
if (v && typeof v === 'object') {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const k of Object.keys(v).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {
|
|
107
|
+
out[k] = sortDeep(v[k]);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
return v;
|
|
112
|
+
};
|
|
113
|
+
return JSON.stringify(sortDeep(value));
|
|
114
|
+
}
|
|
115
|
+
|
|
62
116
|
/**
|
|
63
|
-
* Build
|
|
64
|
-
*
|
|
65
|
-
*
|
|
117
|
+
* Build the canonical EPM signing content. Byte-identical to the in-module
|
|
118
|
+
* verifier (common/epm BuildSigningContent + common/jcs Canonicalize), so a
|
|
119
|
+
* wallet signature over this content verifies isomorphically in the browser and
|
|
120
|
+
* on wasmedge. Mirrors the field set/rules exactly: trim + omit-empty strings,
|
|
121
|
+
* enum-label ENTITY_TYPE (always) / KEY_TYPE (Signing|Encryption only), nested
|
|
122
|
+
* ADDRESS, KEYS/CHAIN_PROOFS arrays, SIGNATURE_TIMESTAMP (integer, when nonzero),
|
|
123
|
+
* and SIGNATURE excluded.
|
|
66
124
|
*
|
|
67
|
-
* @param {Object} epm - EPM fields as a plain object
|
|
68
|
-
*
|
|
125
|
+
* @param {Object} epm - EPM fields as a plain object (schema UPPER_SNAKE keys;
|
|
126
|
+
* ENTITY_TYPE/KEY_TYPE may be enum index or label)
|
|
127
|
+
* @returns {Uint8Array} UTF-8 encoded canonical (JCS) representation
|
|
69
128
|
*/
|
|
70
129
|
export function buildEPMSigningContent(epm) {
|
|
71
|
-
|
|
72
|
-
const {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
const g = (k) => epm[k] ?? epm[k.toLowerCase()];
|
|
131
|
+
const content = {};
|
|
132
|
+
|
|
133
|
+
epmAddStr(content, 'DN', g('DN'));
|
|
134
|
+
epmAddStr(content, 'LEGAL_NAME', g('LEGAL_NAME'));
|
|
135
|
+
epmAddStr(content, 'FAMILY_NAME', g('FAMILY_NAME'));
|
|
136
|
+
epmAddStr(content, 'GIVEN_NAME', g('GIVEN_NAME'));
|
|
137
|
+
epmAddStr(content, 'ADDITIONAL_NAME', g('ADDITIONAL_NAME'));
|
|
138
|
+
epmAddStr(content, 'HONORIFIC_PREFIX', g('HONORIFIC_PREFIX'));
|
|
139
|
+
epmAddStr(content, 'HONORIFIC_SUFFIX', g('HONORIFIC_SUFFIX'));
|
|
140
|
+
epmAddStr(content, 'JOB_TITLE', g('JOB_TITLE'));
|
|
141
|
+
epmAddStr(content, 'OCCUPATION', g('OCCUPATION'));
|
|
142
|
+
epmAddStr(content, 'EMAIL', g('EMAIL'));
|
|
143
|
+
epmAddStr(content, 'TELEPHONE', g('TELEPHONE'));
|
|
144
|
+
|
|
145
|
+
const addr = g('ADDRESS');
|
|
146
|
+
if (addr && typeof addr === 'object') {
|
|
147
|
+
const a = {};
|
|
148
|
+
const ag = (k) => addr[k] ?? addr[k.toLowerCase()];
|
|
149
|
+
epmAddStr(a, 'COUNTRY', ag('COUNTRY'));
|
|
150
|
+
epmAddStr(a, 'REGION', ag('REGION'));
|
|
151
|
+
epmAddStr(a, 'LOCALITY', ag('LOCALITY'));
|
|
152
|
+
epmAddStr(a, 'POSTAL_CODE', ag('POSTAL_CODE'));
|
|
153
|
+
epmAddStr(a, 'STREET', ag('STREET'));
|
|
154
|
+
epmAddStr(a, 'POST_OFFICE_BOX_NUMBER', ag('POST_OFFICE_BOX_NUMBER'));
|
|
155
|
+
if (Object.keys(a).length) content.ADDRESS = a;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
epmAddStrArray(content, 'ALTERNATE_NAMES', g('ALTERNATE_NAMES'));
|
|
159
|
+
|
|
160
|
+
const keys = g('KEYS');
|
|
161
|
+
if (Array.isArray(keys)) {
|
|
162
|
+
const arr = [];
|
|
163
|
+
for (const k of keys) {
|
|
164
|
+
if (!k || typeof k !== 'object') continue;
|
|
165
|
+
const e = {};
|
|
166
|
+
const kg = (kk) => k[kk] ?? k[kk.toLowerCase()];
|
|
167
|
+
epmAddStr(e, 'PUBLIC_KEY', kg('PUBLIC_KEY'));
|
|
168
|
+
epmAddStr(e, 'XPUB', kg('XPUB'));
|
|
169
|
+
epmAddStr(e, 'ADDRESS_TYPE', kg('ADDRESS_TYPE'));
|
|
170
|
+
epmAddStr(e, 'KEY_ADDRESS', kg('KEY_ADDRESS'));
|
|
171
|
+
const kt = epmEnumName(kg('KEY_TYPE'), EPM_KEY_TYPE_NAMES);
|
|
172
|
+
if (kt === 'Signing' || kt === 'Encryption') e.KEY_TYPE = kt;
|
|
173
|
+
if (Object.keys(e).length) arr.push(e);
|
|
174
|
+
}
|
|
175
|
+
if (arr.length) content.KEYS = arr;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
epmAddStrArray(content, 'MULTIFORMAT_ADDRESS', g('MULTIFORMAT_ADDRESS'));
|
|
179
|
+
|
|
180
|
+
// ENTITY_TYPE: always present, verbatim enum label (default User, the FB default).
|
|
181
|
+
const etRaw = g('ENTITY_TYPE');
|
|
182
|
+
const et = etRaw == null ? EPM_ENTITY_TYPE_NAMES[0] : epmEnumName(etRaw, EPM_ENTITY_TYPE_NAMES);
|
|
183
|
+
content.ENTITY_TYPE = typeof et === 'string' ? et : EPM_ENTITY_TYPE_NAMES[0];
|
|
184
|
+
|
|
185
|
+
const ts = g('SIGNATURE_TIMESTAMP');
|
|
186
|
+
const tsNum = Number(ts);
|
|
187
|
+
if (ts != null && Number.isFinite(tsNum) && tsNum !== 0) {
|
|
188
|
+
content.SIGNATURE_TIMESTAMP = Math.trunc(tsNum);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const proofs = g('CHAIN_PROOFS');
|
|
192
|
+
if (Array.isArray(proofs)) {
|
|
193
|
+
const arr = [];
|
|
194
|
+
for (const p of proofs) {
|
|
195
|
+
if (!p || typeof p !== 'object') continue;
|
|
196
|
+
const e = {};
|
|
197
|
+
const pg = (kk) => p[kk] ?? p[kk.toLowerCase()];
|
|
198
|
+
epmAddStr(e, 'CHAIN', pg('CHAIN'));
|
|
199
|
+
epmAddStr(e, 'ADDRESS', pg('ADDRESS'));
|
|
200
|
+
epmAddStr(e, 'PUBLIC_KEY', pg('PUBLIC_KEY'));
|
|
201
|
+
epmAddStr(e, 'KEY_PATH', pg('KEY_PATH'));
|
|
202
|
+
epmAddStr(e, 'SIGNATURE', pg('SIGNATURE'));
|
|
203
|
+
epmAddStr(e, 'SIGNED_PAYLOAD', pg('SIGNED_PAYLOAD'));
|
|
204
|
+
epmAddStr(e, 'ALGORITHM', pg('ALGORITHM'));
|
|
205
|
+
epmAddStr(e, 'ENCODING', pg('ENCODING'));
|
|
206
|
+
if (Object.keys(e).length) arr.push(e);
|
|
207
|
+
}
|
|
208
|
+
if (arr.length) content.CHAIN_PROOFS = arr;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return new TextEncoder().encode(epmJcsCanonicalize(content));
|
|
90
212
|
}
|
|
91
213
|
|
|
92
214
|
/**
|