Skip to content

Commit

Permalink
refactor: Replace lumos with ckb-sdk-js
Browse files Browse the repository at this point in the history
  • Loading branch information
duanyytop committed Dec 12, 2024
1 parent 0ceb255 commit 51a563d
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 39 deletions.
2 changes: 0 additions & 2 deletions packages/ckb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
"dependencies": {
"@joyid/common": "workspace:*",
"@nervosnetwork/ckb-sdk-utils": "^0.109.2",
"@ckb-lumos/base": "0.22.2",
"@ckb-lumos/codec": "0.22.2",
"cross-fetch": "4.0.0",
"uncrypto": "^0.1.3"
}
Expand Down
68 changes: 37 additions & 31 deletions packages/ckb/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,23 @@ import {
append0x,
remove0x,
} from '@joyid/common'
import { blockchain, utils } from '@ckb-lumos/base'
import { bytes, number } from '@ckb-lumos/codec'
import * as ckbUtils from '@nervosnetwork/ckb-sdk-utils'
import { Aggregator } from './aggregator'
import { deserializeWitnessArgs } from './utils'

export * from './verify'

const { addressToScript, blake160, serializeScript } = ckbUtils
const {
PERSONAL,
addressToScript,
blake160,
blake2b,
hexToBytes,
toUint64Le,
serializeScript,
rawTransactionToHash,
serializeWitnessArgs,
} = ckbUtils

const appendPrefix = (tokenKey?: string): string | undefined => {
if (!tokenKey) {
Expand Down Expand Up @@ -259,51 +268,50 @@ export const calculateChallenge = async (
'The first JoyID witness must be serialized hex string of WitnessArgs'
)
}
const transactionHash = bytes.bytify(
utils.ckbHash(blockchain.RawTransaction.pack(tx))
)
const witnessArgs = blockchain.WitnessArgs.unpack(
tx.witnesses[firstWitnessIndex]!
)
const emptyWitness = {
const transactionHash = rawTransactionToHash(tx)
const witnessArgs = deserializeWitnessArgs(tx.witnesses[firstWitnessIndex]!)

const emptyWitness: CKBComponents.WitnessArgs = {
...witnessArgs,
lock: new Uint8Array(SECP256R1_PUBKEY_SIG_LEN),
lock: `0x${'00'.repeat(SECP256R1_PUBKEY_SIG_LEN)}`,
}

const serializedEmptyWitnessBytes = blockchain.WitnessArgs.pack(emptyWitness)
console.log(console.log(emptyWitness))

const serializedEmptyWitnessBytes = hexToBytes(
serializeWitnessArgs(emptyWitness)
)
const serializedEmptyWitnessSize = serializedEmptyWitnessBytes.length

const hasher = new utils.CKBHasher()
hasher.update(transactionHash)
const hasher = blake2b(32, null, null, PERSONAL)
hasher.update(hexToBytes(transactionHash))
hasher.update(
number.Uint64LE.pack(`0x${serializedEmptyWitnessSize.toString(16)}`)
hexToBytes(toUint64Le(`0x${serializedEmptyWitnessSize.toString(16)}`))
)
hasher.update(serializedEmptyWitnessBytes)

for (const witnessIndex of witnessIndexes.slice(1)) {
const witness = witnesses[witnessIndex]
if (witness) {
const arr = bytes.bytify(
typeof witness === 'string'
? witness
: blockchain.WitnessArgs.pack(witness)
const bytes = hexToBytes(
typeof witness === 'string' ? witness : serializeWitnessArgs(witness)
)
hasher.update(number.Uint64LE.pack(`0x${arr.length.toString(16)}`))
hasher.update(arr)
hasher.update(hexToBytes(toUint64Le(`0x${bytes.length.toString(16)}`)))
hasher.update(bytes)
}
}

if (witnesses.length > tx.inputs.length) {
for (const w of witnesses.slice(tx.inputs.length)) {
const arr = bytes.bytify(
typeof w === 'string' ? w : blockchain.WitnessArgs.pack(w)
for (const witness of witnesses.slice(tx.inputs.length)) {
const bytes = hexToBytes(
typeof witness === 'string' ? witness : serializeWitnessArgs(witness)
)
hasher.update(number.Uint64LE.pack(`0x${arr.length.toString(16)}`))
hasher.update(arr)
hasher.update(hexToBytes(toUint64Le(`0x${bytes.length.toString(16)}`)))
hasher.update(bytes)
}
}

const challenge = remove0x(hasher.digestHex())
const challenge = `${hasher.digest('hex')}`
return challenge
}

Expand All @@ -321,16 +329,14 @@ export const buildSignedTx = (
}
const firstWitnessIndex = witnessIndexes[0] ?? 0
const firstWitness = unsignedTx.witnesses[firstWitnessIndex]!
const witnessArgs = blockchain.WitnessArgs.unpack(firstWitness)
const witnessArgs = deserializeWitnessArgs(firstWitness)

const { message, signature, pubkey, keyType } = signedData

const mode = keyType === 'sub_key' ? WITNESS_SUBKEY_MODE : WITNESS_NATIVE_MODE
witnessArgs.lock = `0x${mode}${pubkey}${signature}${message}`

const signedWitness = append0x(
bufferToHex(blockchain.WitnessArgs.pack(witnessArgs))
)
const signedWitness = append0x(serializeWitnessArgs(witnessArgs))

const signedTx = unsignedTx
signedTx.witnesses[firstWitnessIndex] = signedWitness
Expand Down
90 changes: 90 additions & 0 deletions packages/ckb/src/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect } from 'vitest'
import { deserializeWitnessArgs } from './utils'
import { calculateChallenge, CKBTransaction } from '.'

describe('utils', () => {
describe('deserializeWitnessArgs', () => {
it('default_witness_args', async () => {
const witnessArgs = deserializeWitnessArgs(
'0x55000000100000005500000055000000410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
)
expect(witnessArgs.lock).toBe(`0x${'00'.repeat(65)}`)
expect(witnessArgs.inputType).toBe('0x')
expect(witnessArgs.outputType).toBe('0x')
})

it('custom_witness_args', async () => {
const witnessArgs = deserializeWitnessArgs(
'0x1a00000010000000100000001500000001000000100100000020'
)
expect(witnessArgs.lock).toBe('0x')
expect(witnessArgs.inputType).toBe('0x10')
expect(witnessArgs.outputType).toBe('0x20')
})
})

describe('calculateChallenge', () => {
it('verify_challenge_with_raw_tx', async () => {
const ckbTx: CKBComponents.RawTransactionToSign = {
version: '0x00',
cellDeps: [
{
outPoint: {
txHash:
'0x069ae648ecc682caa52b1a6a5854ec3545a8513dd9681f452049a59be33465b0',
index: '0x0',
},
depType: 'depGroup',
},
{
outPoint: {
txHash:
'0xd8c7396f955348bd74a8ed4398d896dad931977b7c1e3f117649765cd3d75b86',
index: '0x0',
},
depType: 'depGroup',
},
],
headerDeps: [],
inputs: [
{
previousOutput: {
txHash:
'0x00ff48f637e12c8aa873d76cd1a9c3e3756f3e7df006760270f23a3a25782f87',
index: '0x1',
},
since: '0x0',
},
],
outputs: [
{
capacity: '0x37e11d0ed',
lock: {
codeHash:
'0xd23761b364210735c19c60561d213fb3beae2fd6172743719eff6920e020baac',
hashType: 'type',
args: '0x0001fd879d61c8187b4fa1e87296bb00371bfebc3a4e',
},
type: {
codeHash:
'0x89cd8003a0eaf8e65e0c31525b7d1d5c1becefd2ea75bb4cff87810ae37764d8',
hashType: 'type',
args: '0x226192f9ca4bcd697bd2fe4429f73a254de25e61',
},
},
],
outputsData: [
'0x020000000000000000000000000000000000000000000000000000000000000000',
],
witnesses: [
'0xc3010000100000001000000010000000af0100007b226964223a2243544d657461222c22766572223a22312e30222c226d65746164617461223a7b22746172676574223a226f75747075742330222c2274797065223a226a6f795f6964222c2264617461223a7b22616c67223a2230783031222c22636f746143656c6c4964223a22307830303030303030303030303030303030222c2263726564656e7469616c4964223a22307837363431643666613336316434326431663830613638396339376430656166376662643336663366303430343332626338313564333932643461366433356162222c2266726f6e745f656e64223a226a6f7969642d6170702d6465762e76657263656c2e617070222c226e616d65223a2273746f6e656b696e67222c227075625f6b6579223a2230786462333135323532356133353836616636346230636638376331636636626633613861663864633961323365383761356161366164316430633665303434376635316631343763363038613831613566383435303936326662383634386465653961636435343863623438626430656438383634326237356530373963616365222c2276657273696f6e223a2230227d7d7d',
],
}

const challenge = await calculateChallenge(ckbTx as CKBTransaction)
expect(challenge).toBe(
'8a4ae0acf8e6481a748627543225f497eaea5d754c9d72cfba8db800ac6d4d1b'
)
})
})
})
29 changes: 29 additions & 0 deletions packages/ckb/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/* eslint-disable unicorn/prefer-string-slice */

import { append0x, remove0x } from '@joyid/common'
import { hexToBytes } from '@nervosnetwork/ckb-sdk-utils'

/**
* Web crypto use IEEE P1363 ECDSA signature format
* ref: https://stackoverflow.com/questions/39554165/ecdsa-signatures-between-node-js-and-webcrypto-appear-to-be-incompatible
Expand All @@ -18,3 +22,28 @@ export function derToIEEE(sig: ArrayBuffer): Uint8Array {
p1363Sig.match(/[\da-f]{2}/gi)!.map((h) => Number.parseInt(h, 16))
)
}

export function leHexStringToU32(hex: string): number {
const bytes = hexToBytes(append0x(hex))
const beHex = `0x${bytes.reduceRight((pre, cur) => pre + cur.toString(16).padStart(2, '0'), '')}`
return Number.parseInt(beHex)
}

export function deserializeWitnessArgs(hex: string): CKBComponents.WitnessArgs {
const args = remove0x(hex)
// full_size(4bytes) + offsets(4bytes * 3) + body(lock + input_type + output_type)
const lockOffset = leHexStringToU32(args.slice(8, 16)) * 2
const inputTypeOffset = leHexStringToU32(args.slice(16, 24)) * 2
const outputTypeOffset = leHexStringToU32(args.slice(24, 32)) * 2

// lock = size(4bytes) + body
const lock = args.slice(lockOffset, inputTypeOffset).slice(8)
const inputType = args.slice(inputTypeOffset, outputTypeOffset).slice(8)
const outputType = args.slice(outputTypeOffset).slice(8)

return {
lock: lock.length === 0 ? '0x' : `0x${lock}`,
inputType: inputType.length === 0 ? '0x' : `0x${inputType}`,
outputType: outputType.length === 0 ? '0x' : `0x${outputType}`,
}
}
6 changes: 0 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 51a563d

Please sign in to comment.