@didtools/pkh-tezos
Version:
Implements support to authenticate, authorize and verify with Tezos accounts as a did:pkh with SIWE(X) and CACAO. Primarly used with `did-session` and `@didtools/cacao`.
105 lines (104 loc) • 3.53 kB
JavaScript
import { SiwTezosMessage, verifyTimeChecks, assertSigned } from '@didtools/cacao';
import { AccountId } from 'caip';
import * as u8a from 'uint8arrays';
import { blake2b } from '@noble/hashes/blake2b';
import { sha256 } from '@noble/hashes/sha256';
import { ed25519 } from '@noble/curves/ed25519';
// ED
const TZ1Prefix = new Uint8Array([
6,
161,
159
]);
const TZ1Length = 20;
const EDPKPrefix = new Uint8Array([
13,
15,
37,
217
]);
const EDSIGPrefix = new Uint8Array([
9,
245,
205,
134,
18
]);
const SIGPrefix = new Uint8Array([
4,
130,
43
]);
const Base58CheckSumLength = 4;
export function getTezosVerifier() {
return {
// eslint-disable-next-line @typescript-eslint/require-await
'tezos:ed25519': async (cacao, opts)=>{
verifyTezosSignature(cacao, opts);
}
};
}
export function getPkhfromPk(publicKey) {
const pkPrefix = publicKey.substring(0, 4);
if (pkPrefix !== 'edpk') throw new Error('Tezos Signature type not supported, only type tezos:ed25519');
const decoded = b58cdecode(publicKey, EDPKPrefix);
const hashed = blake2b(decoded, {
dkLen: TZ1Length
});
const result = b58cencode(hashed, TZ1Prefix);
return result;
}
function verifyEdSignature(decodedSig, bytesHash, decodedPublicKey) {
try {
return ed25519.verify(decodedSig, bytesHash, decodedPublicKey);
} catch (e) {
return false;
}
}
//bs58btc decoding, bs58check - checksum
function b58cdecode(enc, prefixArg) {
const u8akey = u8a.fromString(enc, 'base58btc');
return u8akey.slice(prefixArg.length, u8akey.length - Base58CheckSumLength);
}
//bs58check encoding, bs58btc + checksum
function b58cencode(value, prefix) {
const n = new Uint8Array(prefix.length + value.length);
n.set(prefix);
n.set(value, prefix.length);
const checksum = getCheckSum(n);
const nc = new Uint8Array(n.length + 4);
nc.set(n);
nc.set(checksum, prefix.length + value.length);
return u8a.toString(nc, 'base58btc');
}
function getCheckSum(u8a) {
const hashed = sha256(sha256(u8a));
return hashed.slice(0, 4);
}
export function verifySignature(payload, publicKey, signature) {
const pkPrefix = publicKey.substring(0, 4);
const sigPrefix = signature.startsWith('sig') ? signature.substr(0, 3) : signature.substr(0, 5);
if (pkPrefix !== 'edpk' || !(sigPrefix === 'edsig' || sigPrefix === 'sig')) throw new Error('Tezos Signature type not supported, only type tezos:ed25519');
const decodedPublicKey = b58cdecode(publicKey, EDPKPrefix);
const decodedSig = b58cdecode(signature, sigPrefix === 'edsig' ? EDSIGPrefix : SIGPrefix);
const bytesHash = blake2b(u8a.fromString(payload, 'base16'), {
dkLen: 32
});
return verifyEdSignature(decodedSig, bytesHash, decodedPublicKey);
}
export function verifyTezosSignature(cacao, options) {
assertSigned(cacao);
verifyTimeChecks(cacao, options);
const msg = SiwTezosMessage.fromCacao(cacao);
let signature = cacao.s.s;
const publicKey = signature.slice(99);
signature = signature.slice(0, 99);
const issuerAddress = AccountId.parse(cacao.p.iss.replace('did:pkh:', '')).address;
if (issuerAddress !== getPkhfromPk(publicKey)) {
throw new Error(`address does not belong to publicKey`);
}
const payload = msg.signMessage();
if (!verifySignature(payload, publicKey, signature)) {
throw new Error(`Signature does not belong to issuer`);
}
}