iso-filecoin
Version:
Isomorphic filecoin abstractions for RPC, signatures, address, token and wallet
417 lines (379 loc) • 11.6 kB
JavaScript
;
var bls12381 = require('../node_modules/.pnpm/@noble_curves@1.9.4/node_modules/@noble/curves/esm/bls12-381.cjs');
var secp256k1 = require('../node_modules/.pnpm/@noble_curves@1.9.4/node_modules/@noble/curves/esm/secp256k1.cjs');
var blake2b = require('../node_modules/.pnpm/@noble_hashes@1.8.0/node_modules/@noble/hashes/esm/blake2b.cjs');
var index$1 = require('../node_modules/.pnpm/@scure_bip32@1.7.0/node_modules/@scure/bip32/lib/esm/index.cjs');
var index = require('../node_modules/.pnpm/@scure_bip39@1.6.0/node_modules/@scure/bip39/esm/index.cjs');
var english = require('../node_modules/.pnpm/@scure_bip39@1.6.0/node_modules/@scure/bip39/esm/wordlists/english.cjs');
var rfc4648 = require('../node_modules/.pnpm/iso-base@4.1.0/node_modules/iso-base/src/rfc4648.cjs');
var utf8 = require('../node_modules/.pnpm/iso-base@4.1.0/node_modules/iso-base/src/utf8.cjs');
var utils = require('../node_modules/.pnpm/iso-base@4.1.0/node_modules/iso-base/src/utils.cjs');
var src_address = require('./address.cjs');
var src_message = require('./message.cjs');
var src_signature = require('./signature.cjs');
var src_utils = require('./utils.cjs');
var schemas = require('../node_modules/.pnpm/zod@4.0.10/node_modules/zod/v4/classic/schemas.cjs');
/**
* @import {SetRequired} from 'type-fest'
*/
/**
* FRC-102 prefix
*
* @see https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0102.md
*/
const FRC_102_PREFIX = '\x19Filecoin Signed Message:\n';
/**
* Schemas
*/
const Schemas = {
lotusPrivateKey: schemas.object({
Type: schemas.union([schemas.literal('bls'), schemas.literal('secp256k1')]),
/**
* Lotus BLS private key is little endian so you need to reverse the byte order.
* base64pad(private-key)
*/
PrivateKey: schemas.string(),
}),
};
/**
* Generate mnemonic
*/
function generateMnemonic() {
return index.generateMnemonic(english.wordlist, 256)
}
/**
* Get seed from mnemonic
*
* @param {string} mnemonic
* @param {string} [password]
*/
function mnemonicToSeed(mnemonic, password) {
return index.mnemonicToSeedSync(mnemonic, password)
}
/**
* Get HD account from mnemonic
*
* @param {string} mnemonic
* @param {import('./types.js').SignatureType} type
* @param {string} path
* @param {string} [password]
* @param {import('./types.js').Network} [network]
*/
function accountFromMnemonic(mnemonic, type, path, password, network) {
const seed = mnemonicToSeed(mnemonic, password);
return accountFromSeed(seed, type, path, network)
}
/**
* Get HD account from seed
*
* @param {Uint8Array} seed
* @param {import('./types.js').SignatureType} type
* @param {string} path
* @param {import('./types.js').Network} [network]
* @returns {SetRequired<import('./types.js').IAccount, 'privateKey' | 'path'>}
*/
function accountFromSeed(seed, type, path, network) {
const masterKey = index$1.HDKey.fromMasterSeed(seed);
const privateKey = masterKey.derive(path).privateKey;
if (!privateKey) {
throw new Error('Private key could not be generated.')
}
if (!network) {
network = src_utils.getNetworkFromPath(path);
}
const { address, publicKey } = getPublicKey(privateKey, network, type);
return {
type,
privateKey,
publicKey,
address,
path,
}
}
/**
* Get account from private key
*
* Lotus BLS private key is little endian so you need to reverse the byte order. Use `lotusBlsPrivateKeyToBytes` to convert.
*
* @param {Uint8Array} privateKey
* @param {import('./types.js').SignatureType} type
* @param {import('./types.js').Network} network
* @param {string} [path]
* @returns {SetRequired<import('./types.js').IAccount, 'privateKey'>}
*/
function accountFromPrivateKey(privateKey, type, network, path) {
if (privateKey.length !== 32) {
throw new Error('Private key should be 32 bytes.')
}
const { address, publicKey } = getPublicKey(privateKey, network, type);
return {
type,
privateKey,
publicKey,
address,
path,
}
}
/**
* Get account from lotus private key export
*
* @param {string} lotusHex - Lotus hex encoded private key .ie `hex({"Type":"bls","PrivateKey":"base64pad(private-key)"})`
* @param {import('./types.js').Network} network - Network
* @returns {import('./types.js').IAccount}
*/
function accountFromLotus(lotusHex, network) {
const lotusJson = Schemas.lotusPrivateKey.parse(
JSON.parse(utf8.utf8.encode(rfc4648.hex.decode(lotusHex)))
);
if (lotusJson.Type === 'bls') {
return accountFromPrivateKey(
lotusBlsPrivateKeyToBytes(lotusJson.PrivateKey),
'BLS',
network
)
}
return accountFromPrivateKey(
rfc4648.base64pad.decode(lotusJson.PrivateKey),
'SECP256K1',
network
)
}
/**
* Create account
*
* @param {import('./types.js').SignatureType} type
* @param {import('./types.js').Network} network
* @returns {SetRequired<import('./types.js').IAccount, 'privateKey'>}
*/
function create(type, network) {
switch (type) {
case 'SECP256K1': {
return accountFromPrivateKey(secp256k1.secp256k1.utils.randomPrivateKey(), type, network)
}
case 'BLS': {
return accountFromPrivateKey(bls12381.bls12_381.utils.randomPrivateKey(), type, network)
}
default: {
throw new Error(
`Create does not support "${type}" type. Use SECP256K1 or BLS.`
)
}
}
}
/**
* Get public key from private key
*
* @param {Uint8Array} privateKey
* @param {import('./types.js').Network} network
* @param {import('./types.js').SignatureType} type
* @returns {import('./types.js').IAccount}
*/
function getPublicKey(privateKey, network, type) {
switch (type) {
case 'SECP256K1': {
const publicKey = secp256k1.secp256k1.getPublicKey(privateKey, false);
return {
type: 'SECP256K1',
publicKey,
address: src_address.fromPublicKey(publicKey, network, 'SECP256K1'),
}
}
case 'BLS': {
const publicKey = bls12381.bls12_381.getPublicKey(privateKey);
return {
type: 'BLS',
publicKey,
address: src_address.fromPublicKey(publicKey, network, 'BLS'),
}
}
default: {
throw new Error(
`getPublicKey does not support "${type}" type. Use SECP256K1 or BLS.`
)
}
}
}
/**
* Sign filecoin message
*
* @param {Uint8Array} privateKey
* @param {import('./types.js').SignatureType} type
* @param {import('./types.js').MessageObj} message
* @returns
*/
function signMessage(privateKey, type, message) {
const cid = new src_message.Message(message).cidBytes();
return sign(privateKey, type, cid)
}
/**
* Sign arbitary bytes similar to `lotus wallet sign`
*
* Lotus BLS private key is little endian so you need to reverse the byte order. Use `lotusBlsPrivateKeyToBytes` to convert.
*
* @param {Uint8Array} privateKey
* @param {import('./types.js').SignatureType} type
* @param {Uint8Array} data
*/
function sign(privateKey, type, data) {
switch (type) {
case 'SECP256K1': {
const signature = secp256k1.secp256k1.sign(
blake2b.blake2b(data, {
dkLen: 32,
}),
privateKey
);
return new src_signature.Signature({
type: 'SECP256K1',
data: utils.concat([
signature.toCompactRawBytes(),
Uint8Array.from([signature.recovery]),
]),
})
}
case 'BLS': {
const signature = bls12381.bls12_381.sign(data, privateKey);
return new src_signature.Signature({
type: 'BLS',
data: signature,
})
}
default: {
throw new Error(
`Sign does not support "${type}" type. Use SECP256K1 or BLS.`
)
}
}
}
/**
* Personal sign using FRC-102
*
* @see https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0102.md
*
* @param {Uint8Array} privateKey
* @param {import('./types.js').SignatureType} type
* @param {Uint8Array} data
*/
function personalSign(privateKey, type, data) {
const prefix = utf8.utf8.decode(`${FRC_102_PREFIX}${data.length}`);
return sign(privateKey, type, utils.concat([prefix, data]))
}
/**
* Personal verify using FRC-102
*
* @see https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0102.md
*
* @param {import('./signature.js').Signature} signature
* @param {Uint8Array} data
* @param {Uint8Array} publicKey
*/
function personalVerify(signature, data, publicKey) {
const prefix = utf8.utf8.decode(`${FRC_102_PREFIX}${data.length}`);
return verify(signature, utils.concat([prefix, data]), publicKey)
}
/**
* Verify signatures
*
* @param {import('./signature.js').Signature} signature
* @param {Uint8Array} data
* @param {Uint8Array} publicKey
*/
function verify(signature, data, publicKey) {
switch (signature.type) {
case 'SECP256K1': {
return secp256k1.secp256k1.verify(
secp256k1.secp256k1.Signature.fromCompact(signature.data.subarray(0, 64)),
blake2b.blake2b(data, {
dkLen: 32,
}),
publicKey
)
}
case 'BLS': {
return bls12381.bls12_381.verify(signature.data, data, publicKey)
}
default: {
throw new Error(
`Verify does not support "${signature.type}" type. Use SECP256K1 or BLS.`
)
}
}
}
/**
* Lotus BLS base64 private key to bytes
* Lotus BLS private key is little endian so you need to reverse the byte order.
*
* @param {string} priv
*/
function lotusBlsPrivateKeyToBytes(priv) {
return rfc4648.base64pad.decode(priv).reverse()
}
/**
*
* @param {Signature} signature
* @param {Uint8Array} data
*/
function recoverPublicKey(signature, data) {
if (signature.type === 'BLS') {
throw new Error('Recover public key is not supported for BLS')
}
const hash = blake2b.blake2b(data, {
dkLen: 32,
});
return secp256k1.secp256k1.Signature.fromCompact(signature.data.subarray(0, 64))
.addRecoveryBit(signature.data[64])
.recoverPublicKey(hash)
.toRawBytes(false)
}
/**
*
* @param {Signature} signature
* @param {Uint8Array} data
* @param {import('./types.js').Network} network
*/
function recoverAddress(signature, data, network) {
const publicKey = recoverPublicKey(signature, data);
return src_address.fromPublicKey(publicKey, network, 'SECP256K1')
}
/**
* Export account to lotus private key export format (hex)
*
* @param {import('./types.js').IAccount} account
*/
function accountToLotus(account) {
if (account.privateKey == null) {
throw new Error('Private key not found')
}
if (account.type === 'BLS') {
return rfc4648.hex.encode(
JSON.stringify({
Type: 'bls',
PrivateKey: rfc4648.base64pad.encode(account.privateKey?.reverse()),
})
)
}
return rfc4648.hex.encode(
JSON.stringify({
Type: 'secp256k1',
PrivateKey: rfc4648.base64pad.encode(account.privateKey),
})
)
}
exports.Schemas = Schemas;
exports.accountFromLotus = accountFromLotus;
exports.accountFromMnemonic = accountFromMnemonic;
exports.accountFromPrivateKey = accountFromPrivateKey;
exports.accountFromSeed = accountFromSeed;
exports.accountToLotus = accountToLotus;
exports.create = create;
exports.generateMnemonic = generateMnemonic;
exports.getPublicKey = getPublicKey;
exports.lotusBlsPrivateKeyToBytes = lotusBlsPrivateKeyToBytes;
exports.mnemonicToSeed = mnemonicToSeed;
exports.personalSign = personalSign;
exports.personalVerify = personalVerify;
exports.recoverAddress = recoverAddress;
exports.recoverPublicKey = recoverPublicKey;
exports.sign = sign;
exports.signMessage = signMessage;
exports.verify = verify;