pithos/test/sia-account.test.js
Local Dev afb74362b8 Pithos: show which Sia account an s3d is connected to
A person with several sia.storage accounts (or phrases) could not tell
which one a Pithos s3d used: s3d records only the indexer URL. Pithos now
reads the app key s3d stores after login and asks the indexer, with a
request signed the way indexd's app API expects, for the account's key,
storage used and last use. Those match what sia.storage shows for the app,
and the page explains that the login and the phrase together decide the
account. Earlier accounts set aside on this computer show their key too.
2026-10-04 03:19:02 +02:00

42 lines
2.4 KiB
JavaScript

// Telling which Sia account an s3d uses: BLAKE2b-256 against RFC 7693, and
// requests signed the way indexd's app API checks them (api/app/auth.go).
import test from 'node:test';
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import { blake2b, blake2b256 } from '../core/blake2b.js';
import { signRequest, fingerprint } from '../core/sia-account.js';
test('BLAKE2b matches RFC 7693 and OpenSSL', () => {
assert.equal(blake2b256('').toString('hex'), '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8');
assert.equal(blake2b('abc', 64).toString('hex'), 'ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923');
for (const n of [1, 127, 128, 129, 255, 256, 257, 1000]) {
const b = crypto.randomBytes(n);
assert.ok(blake2b(b, 64).equals(crypto.createHash('blake2b512').update(b).digest()), `length ${n}`);
}
});
test('signed /account request: what indexd verifies', () => {
// A Go types.PrivateKey: 32-byte seed followed by the public key.
const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
const seed = privateKey.export({ format: 'der', type: 'pkcs8' }).subarray(-32);
const pub = publicKey.export({ format: 'der', type: 'spki' }).subarray(-32);
const appKey = Buffer.concat([seed, pub]);
const sv = 1_900_000_000;
const u = signRequest(appKey, 'GET', 'https://sia.storage/account', sv);
const q = u.searchParams;
// Go's base64.URLEncoding: URL-safe alphabet, with padding.
assert.match(q.get('sc'), /^[A-Za-z0-9_-]{43}=$/);
assert.match(q.get('ss'), /^[A-Za-z0-9_-]{86}==$/);
assert.equal(q.get('sv'), String(sv));
assert.ok(Buffer.from(q.get('sc'), 'base64').equals(pub));
const exp = Buffer.alloc(8);
exp.writeBigUInt64LE(BigInt(sv));
const digest = blake2b256(Buffer.concat([Buffer.from('GET'), Buffer.from('sia.storage'), Buffer.from('/account'), exp]));
assert.ok(crypto.verify(null, digest, publicKey, Buffer.from(q.get('ss'), 'base64')));
// Host and path come from the indexer URL, port included.
const v = signRequest(appKey, 'GET', 'http://127.0.0.1:9880/base/account', sv);
const d2 = blake2b256(Buffer.concat([Buffer.from('GET127.0.0.1:9880/base/account'), exp]));
assert.ok(crypto.verify(null, d2, publicKey, Buffer.from(v.searchParams.get('ss'), 'base64')));
assert.equal(fingerprint('3cc1a2f2' + '0'.repeat(52) + 'ac52'), 'ed25519:3cc1a2f2…ac52');
});