Closed Keith-CY closed 1 year ago
private static signByPrivateKey(privateKey: string, message: string): string {
const digest = SignMessage.signatureHash(message)
const ecPair = new ECPair(privateKey)
const signature = ecPair.signRecoverable(digest)
return signature
}
private static signatureHash(message: string) {
const buffer = Buffer.from(SignMessage.magicString + message, 'utf-8')
const blake2b = new Blake2b()
blake2b.updateBuffer(buffer)
return blake2b.digest()
}
private static magicString = 'Nervos Message:'
const options = {
r: signature.slice(2, 66),
s: signature.slice(66, 130),
recoveryParam: parseInt(signature.slice(-1))
}
const msgBuffer = Buffer.from(digest.slice(2), 'hex')
const publicKey ='0x' + SignMessage.ec.recoverPubKey(msgBuffer, options, options.recoveryParam).encode('hex', true)
Ref:
/**
* @description payload to a full address of new version
*/
const payloadToAddress = (payload: Uint8Array, isMainnet = true) =>
bech32m.encode(isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, bech32m.toWords(payload), MAX_BECH32_LIMIT)
const scriptToPayload = ({ codeHash, hashType, args }: CKBComponents.Script): Uint8Array => {
if (!args.startsWith('0x')) {
throw new HexStringWithout0xException(args)
}
if (!codeHash.startsWith('0x') || codeHash.length !== 66) {
throw new CodeHashException(codeHash)
}
enum HashType {
data = '00',
type = '01',
data1 = '02',
}
if (!HashType[hashType]) {
throw new HashTypeException(hashType)
}
return hexToBytes(`0x00${codeHash.slice(2)}${HashType[hashType]}${args.slice(2)}`)
}
/**
* @deprecated please migrate to {@link https://lumos-website.vercel.app/api/modules/helpers.html#encodetoaddress @ckb-lumos/helpers/encodeToAddress} {@link https://lumos-website.vercel.app/migrations/migrate-form-ckb-sdk-utils#scripttoaddress example}
* @function scriptToAddress
* @description The only way recommended to generated a full address of new version
* @param {object} script
* @param {booealn} isMainnet
* @returns {string} address
*/
export const scriptToAddress = (script: CKBComponents.Script, isMainnet = true) =>
payloadToAddress(scriptToPayload(script), isMainnet)
Ref:
import { ec as EC } from 'elliptic'
import { blake2b, PERSONAL, scriptToAddress, systemScripts } from '@nervosnetwork/ckb-sdk-utils'
import blake160 from '@nervosnetwork/ckb-sdk-utils/lib/crypto/blake160'
const ec = new EC('secp256k1')
const BLAKE_2B_SIZE = 32
const MAGIC_STRING = `Nervos Message:`
/**
* the progress of signing a message
*/
const signMessage = (message: string, sk: string) => {
const msgToSign = Buffer.from(MAGIC_STRING + message, 'utf8')
const digest = blake2b(BLAKE_2B_SIZE, null, null, PERSONAL).update(msgToSign).digest('binary')
const ecPair = ec.keyFromPrivate(sk.replace(/^0x/, ''))
const { r, s, recoveryParam } = ecPair.sign(digest, { canonical: true })
if (recoveryParam === null) throw new Error()
const fmtR = r.toString(16).padStart(64, '0')
const fmtS = s.toString(16).padStart(64, '0')
return `0x${fmtR}${fmtS}0${recoveryParam}`
}
/**
* the progress of recovering the public key
*/
const recoverPk = (message: string, signature: string) => {
const r = signature.slice(2, 66)
const s = signature.slice(66, 130)
const recoveryParam = parseInt(signature.slice(-1))
const msg = Buffer.from(MAGIC_STRING + message, 'utf8')
const digest = blake2b(BLAKE_2B_SIZE, null, null, PERSONAL).update(msg).digest('binary')
return `0x` + ec.recoverPubKey(digest, { r, s }, recoveryParam).encode('hex', true)
}
/**
* test cases from https://github.com/nervosnetwork/neuron/blob/89666d10454698f693aadad69078eda7ed17d7dc/packages/neuron-wallet/tests/services/sign-message.test.ts#L36-L116
*/
const fixture = {
privateKey: '0xe79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3',
message: 'HelloWorld',
digest: '0xdfb48ccf7126479c052f68cb4202cd094632d30198a322e3c3638679bc73858d',
address: 'ckb1qyqrdsefa43s6m882pcj53m4gdnj4k440axqdt9rtd',
signture:
'0x97ed8c48879eed50743532bf7cc53e641c501509d2be19d06e6496dd944a21b4509136f18c8e139cc4002822b2deb5cbaff8e44b8782769af3113ff7fb8bd92700',
}
/**
* the address in fixture is in a deprecated format so it should be transformed to the effective one
* the effective one could be found on Explorer: https://explorer.nervos.org/address/ckb1qyqrdsefa43s6m882pcj53m4gdnj4k440axqdt9rtd and click the "view in new address format" button next to the address
* or it can be computed by `scriptToAddress(addressToScript(fixture.address))`
*/
const fixtureAddress = `ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqfkcv576ccddnn4quf2ga65xee2m26h7nqmzxl9m`
/**
* Test signed message
*/
const signedMessage = signMessage(fixture.message, fixture.privateKey)
console.log(`Expect to have the same signed message: ${signedMessage === fixture.signture}`)
/**
* Test recovered public key
*/
const recoveredPk = recoverPk(fixture.message, fixture.signture)
console.log(
`Expect to have the same pk: ${
ec.keyFromPrivate(fixture.privateKey.slice(2)).getPublic(true, 'hex') === recoveredPk.slice(2)
}`
)
/**
* Test recovered address
*/
const secp256k1ScriptArgs = '0x' + Buffer.from(blake160(recoveredPk)).toString('hex')
const recoveredAddress = scriptToAddress({ ...systemScripts.SECP256K1_BLAKE160, args: secp256k1ScriptArgs })
console.log(`Expect to have the same address: ${fixtureAddress === recoveredAddress}`)
Metaforo is going to support DAO of Nervos and it requires users in the community to provide a message signed by Neuron to prove identities.
The public key recovered from the signed message will be the authenticated token as a member so the code snippet to recover a pk from a message is required.
Besides, a doc of how the message is signed is helpful