@dwn-protocol/id-sdk
Version:
SDK for accessing the features and capabilities
115 lines (91 loc) • 4.17 kB
text/typescript
import type { IDCrypto } from '../types/iddwn-crypto.js';
import type { BytesKeyPair } from '../types/crypto-key.js';
import { isBytesKeyPair } from '../utils.js';
import { Secp256k1, X25519 } from '../crypto-primitives/index.js';
import { CryptoKey, BaseEcdhAlgorithm, OperationError } from '../algorithms-api/index.js';
export class EcdhAlgorithm extends BaseEcdhAlgorithm {
public readonly namedCurves = ['secp256k1', 'X25519'];
public async deriveBits(options: {
algorithm: IDCrypto.EcdhDeriveKeyOptions,
baseKey: IDCrypto.CryptoKey,
length: number | null
}): Promise<Uint8Array> {
const { algorithm, baseKey, length } = options;
this.checkAlgorithmOptions({ algorithm, baseKey });
// The base key must be allowed to be used for deriveBits operations.
this.checkKeyUsages({ keyUsages: ['deriveBits'], allowedKeyUsages: baseKey.usages });
// The public key must be allowed to be used for deriveBits operations.
this.checkKeyUsages({ keyUsages: ['deriveBits'], allowedKeyUsages: algorithm.publicKey.usages });
let sharedSecret: Uint8Array;
const ownKeyAlgorithm = baseKey.algorithm as IDCrypto.EcGenerateKeyOptions; // Type guard.
switch (ownKeyAlgorithm.namedCurve) {
case 'secp256k1': {
const ownPrivateKey = baseKey.material;
const otherPartyPublicKey = algorithm.publicKey.material;
sharedSecret = await Secp256k1.sharedSecret({
privateKey : ownPrivateKey,
publicKey : otherPartyPublicKey
});
break;
}
case 'X25519': {
const ownPrivateKey = baseKey.material;
const otherPartyPublicKey = algorithm.publicKey.material;
sharedSecret = await X25519.sharedSecret({
privateKey : ownPrivateKey,
publicKey : otherPartyPublicKey
});
break;
}
default:
throw new TypeError(`Out of range: '${ownKeyAlgorithm.namedCurve}'. Must be one of '${this.namedCurves.join(', ')}'`);
}
// Length is null, return the full derived secret.
if (length === null)
return sharedSecret;
// If the length is not a multiple of 8, throw.
if (length && length % 8 !== 0)
throw new OperationError(`To be compatible with all browsers, 'length' must be a multiple of 8.`);
// Convert length from bits to bytes.
const lengthInBytes = length / 8;
// If length (converted to bytes) is larger than the derived secret, throw.
if (sharedSecret.byteLength < lengthInBytes)
throw new OperationError(`Requested 'length' exceeds the byte length of the derived secret.`);
// Otherwise, either return the secret or a truncated slice.
return lengthInBytes === sharedSecret.byteLength ?
sharedSecret :
sharedSecret.slice(0, lengthInBytes);
}
public async generateKey(options: {
algorithm: IDCrypto.EcGenerateKeyOptions | IDCrypto.EcdsaGenerateKeyOptions,
extractable: boolean,
keyUsages: IDCrypto.KeyUsage[]
}): Promise<IDCrypto.CryptoKeyPair> {
const { algorithm, extractable, keyUsages } = options;
this.checkGenerateKey({ algorithm, keyUsages });
let keyPair: BytesKeyPair | undefined;
let cryptoKeyPair: IDCrypto.CryptoKeyPair;
switch (algorithm.namedCurve) {
case 'secp256k1': {
(algorithm as IDCrypto.EcdsaGenerateKeyOptions).compressedPublicKey ??= true;
keyPair = await Secp256k1.generateKeyPair({
compressedPublicKey: (algorithm as IDCrypto.EcdsaGenerateKeyOptions).compressedPublicKey
});
break;
}
case 'X25519': {
keyPair = await X25519.generateKeyPair();
break;
}
// Default case not needed because checkGenerateKey() already validates the specified namedCurve is supported.
}
if (!isBytesKeyPair(keyPair)) {
throw new Error('Operation failed to generate key pair.');
}
cryptoKeyPair = {
privateKey : new CryptoKey(algorithm, extractable, keyPair.privateKey, 'private', this.keyUsages.privateKey),
publicKey : new CryptoKey(algorithm, true, keyPair.publicKey, 'public', this.keyUsages.publicKey)
};
return cryptoKeyPair;
}
}