@dioxide-js/silas
Version:
RPC utility for Silas
175 lines (148 loc) • 5.21 kB
text/typescript
import { dataview } from '@dioxide-js/misc'
import * as ed from '@noble/ed25519'
import base32Encode from 'base32-encode'
import sha256 from 'sha256'
import crc32c from 'crc-32/crc32c.js'
import crypto from 'crypto'
import GenericAddress, { encodeMnemonic, EncryptMethod, formatedSalt } from './base'
import { toUint8Array } from '../buffer'
export interface IaddressDetails {
currency: string
address: Uint8Array
encryptMethod: string
encryptMethodOrderNumber: number
salt: number
alias?: string
}
interface Options {
privateKey?: Uint8Array
salt?: Uint8Array
seed?: Uint8Array
}
export default class DIOEd25519 implements GenericAddress {
private privateKey?: string
private privateKeyU8?: Uint8Array = new Uint8Array()
encryptMethod: EncryptMethod = 'ed25519'
encryptOrderNum = 3
saltRef = 1
salt = new Uint8Array()
seed = new Uint8Array()
alias = 'Address1'
words: string[] = []
constructor(options?: Options) {
if (options?.privateKey) {
const { privateKey } = options
const sk = toUint8Array(privateKey)
this.salt = formatedSalt(this.saltRef)
this.seed = this.generateSeed()
this.privateKeyU8 = this.get32bPrivateKey(sk)
this.privateKey = dataview.u8ToHex(this.privateKeyU8)
}
}
generateSeed(seed?: number[]) {
let seedU8: number[] | Uint8Array = []
if (!seed) {
const tableContainer = new Uint8Array(27)
const randomData = crypto.getRandomValues(tableContainer)
seedU8 = randomData // sha256(randomData as Buffer, { asBytes: true })
} else {
seedU8 = seed
}
this.words = encodeMnemonic(seedU8)
return new Uint8Array(seedU8)
}
async generate() {
const [pku8, sku8] = await this.keyPaires()
const addr = this.pkToAddress(pku8, true)
const sk = dataview.u8ToHex(sku8)
const pk = dataview.u8ToHex(pku8)
const address = addr.toLowerCase()
return { publickey: pk, privatekey: sk, pku8, sku8, address }
}
getPubicKeyFromPrivateKey(privateKey: string | Uint8Array): Promise<Uint8Array> {
if (typeof privateKey === 'string') {
privateKey = dataview.hexToU8(privateKey)
}
return ed.getPublicKey(privateKey)
}
private pkToDIOStruct(publicKey: Uint8Array, salt = 1, alias?: string) {
const order = this.encryptOrderNum
const method = this.encryptMethod
let errorCorrectingCode = crc32c.buf(publicKey, order)
errorCorrectingCode = (errorCorrectingCode & 0xfffffff0) | order
errorCorrectingCode = errorCorrectingCode >>> 0
const buffer = new Int32Array([errorCorrectingCode]).buffer
const errorCorrectingCodeBuffer = new Uint8Array(buffer)
const mergedBuffer = dataview.concat(publicKey, errorCorrectingCodeBuffer)
const address = {
currency: 'DIO',
address: mergedBuffer,
encryptMethod: method,
encryptMethodOrderNumber: order,
salt: salt || 1,
alias: alias || `Address${salt}`,
}
return address
}
private async keyPaires(): Promise<[Uint8Array, Uint8Array]> {
let publicKey: Uint8Array
let privateKey: Uint8Array
if (this.privateKey && this.privateKeyU8) {
privateKey = this.privateKeyU8
publicKey = await ed.getPublicKey(privateKey)
} else {
const [pk, sk] = await seed2PairOfKey(this.seed, this.salt)
publicKey = pk
privateKey = sk
}
return [publicKey, privateKey]
}
pkToAddrU8(publickey: Uint8Array): Uint8Array {
if (publickey.length !== 32) {
throw 'expect 32 bytes publick key'
}
const o = this.pkToDIOStruct(publickey)
return o.address
}
pkToAddress(publickey: Uint8Array, postfix = true): string {
const u8 = this.pkToAddrU8(publickey)
const address = base32Encode(u8, 'Crockford').toLowerCase()
return postfix ? address + ':' + this.encryptMethod : address
}
async sign(content: string | Uint8Array | number[], privateKey: Uint8Array) {
if (Array.isArray(content)) {
const message = Uint8Array.from(content)
return ed.sign(message, privateKey)
}
privateKey = this.get32bPrivateKey(privateKey)
return ed.sign(content, privateKey)
}
private get32bPrivateKey(sk: Uint8Array) {
const l = sk.length
if (!(l === 64 || l === 32)) {
throw `Illegal PrivateKey format, expect 32 bytes 64 bytes`
}
return sk.length === 64 ? sk.slice(0, 32) : sk
}
verify(msg: string | Uint8Array | number[], sigValueHex: string, publicKey: Uint8Array) {
const pk = dataview.u8ToHex(publicKey)
if (Array.isArray(msg)) {
msg = Uint8Array.from(msg)
msg = dataview.u8ToHex(msg)
}
return ed.verify(sigValueHex, msg, pk)
}
}
async function seed2PairOfKey(
seed: Uint8Array,
salt: Uint8Array = new Uint8Array(),
): Promise<[Uint8Array, Uint8Array]> {
const formatedSeed = dataview.concat(seed, salt)
const privateKeyStr = sha256(formatedSeed as Buffer, { asBytes: true })
const privateKey = new Uint8Array(privateKeyStr)
const publicKey = await ed.getPublicKey(privateKey)
return [publicKey, privateKey]
}
export function encodeAddressBuffer(address: Uint8Array): string {
return base32Encode(address, 'Crockford').toLocaleLowerCase()
}