Skip to content

Commit

Permalink
feat: switch to exodus/crypto for secp256k1 (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChALkeR authored Oct 2, 2024
1 parent 967abe0 commit 292dd33
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 24 deletions.
25 changes: 24 additions & 1 deletion features/keychain/module/__tests__/ecdsa.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,32 @@ describe('EcDSA Signer Signature Encoding', () => {
expect(signature.toString('hex')).toBe(expected.binary)
})

it('sig|rec encoding', async () => {
const signature = await keychain.secp256k1.signBuffer({ seedId, keyId, data, enc: 'sig|rec' })
expect(signature instanceof Buffer).toBe(true)
expect(signature.toString('hex')).toBe(expected.binary)
})

it('rec|sig encoding', async () => {
const signature = await keychain.secp256k1.signBuffer({ seedId, keyId, data, enc: 'rec|sig' })
expect(signature instanceof Buffer).toBe(true)
const recsig = expected.binary.slice(128) + expected.binary.slice(0, 128)
expect(signature.toString('hex')).toBe(recsig)
})

it('sig,rec encoding', async () => {
const res = await keychain.secp256k1.signBuffer({ seedId, keyId, data, enc: 'sig,rec' })
expect(typeof res === 'object').toBe(true)
const { signature, recovery } = res
expect(signature instanceof Buffer).toBe(true)
expect(signature.toString('hex')).toBe(expected.binary.slice(0, 128))
expect(recovery === 0 || recovery === 1).toBe(true)
expect(recovery).toBe(Buffer.from(expected.binary, 'hex')[64])
})

it('Raw encoding', async () => {
const signature = await keychain.secp256k1.signBuffer({ seedId, keyId, data, enc: 'raw' })
expect(typeof signature === 'object')
expect(typeof signature === 'object').toBe(true)
expect(Object.getOwnPropertyNames(signature)).toStrictEqual(['r', 's', 'recoveryParam'])
const r = Buffer.from(signature.r.toArray('be', 32))
const s = Buffer.from(signature.s.toArray('be', 32))
Expand Down
47 changes: 29 additions & 18 deletions features/keychain/module/crypto/secp256k1.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
import assert from 'minimalistic-assert'
import * as secp256k1 from '@exodus/crypto/secp256k1'
import elliptic from '@exodus/elliptic'
import { mapValues } from '@exodus/basic-utils'
import BN from 'bn.js'

import { tweakPrivateKey } from './tweak.js'

const encodeSignature = ({ signature, enc }) => {
if (enc === 'der') return Buffer.from(signature.toDER())

if (enc === 'raw') return { ...signature }

const r = Buffer.from(signature.r.toArray('be', 32))
const s = Buffer.from(signature.s.toArray('be', 32))
return Buffer.concat([r, s, Buffer.from([signature.recoveryParam])])
}

export const create = ({ getPrivateHDKey }) => {
const EC = elliptic.ec
const curve = new EC('secp256k1')

const createInstance = () => ({
signBuffer: async ({ seedId, keyId, data, enc = 'der' }) => {
signBuffer: async ({ seedId, keyId, data, enc = 'der', extraEntropy = null }) => {
assert(
keyId.keyType === 'secp256k1',
`ECDSA signatures are not supported for ${keyId.keyType}`
)
assert(['der', 'raw', 'binary'].includes(enc), 'signBuffer: invalid enc')
if (enc === 'binary') enc = 'sig|rec'
assert(
['der', 'raw', 'sig', 'sig|rec', 'rec|sig', 'sig,rec'].includes(enc),
'signBuffer: invalid enc'
)
const { privateKey } = getPrivateHDKey({ seedId, keyId })
const signature = curve.sign(data, privateKey, { canonical: true })
return encodeSignature({ signature, enc })
const res = await secp256k1.ecdsaSignHash({
hash: data,
privateKey,
extraEntropy,
der: enc === 'der',
recovery: enc !== 'der' && enc !== 'sig',
format: 'buffer',
})
if (enc === 'der' || enc === 'sig' || enc === 'sig,rec') return res
const { signature, recovery } = res
if (enc === 'sig|rec') return Buffer.concat([signature, new Uint8Array([recovery])])
if (enc === 'rec|sig') return Buffer.concat([new Uint8Array([recovery]), signature])
if (enc === 'raw') {
// Deprecated, compatibility mode with manual signature encoding
return {
r: new BN(signature.subarray(0, signature.length / 2)),
s: new BN(signature.subarray(signature.length / 2)),
recoveryParam: recovery,
}
}

throw new Error('Unreachable')
},
signSchnorr: async ({ seedId, keyId, data, tweak, extraEntropy }) => {
assert(
Expand Down
4 changes: 1 addition & 3 deletions features/keychain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,19 @@
"@exodus/basic-utils": "^3.0.1",
"@exodus/bip32": "^2.1.0",
"@exodus/crypto": "^1.0.0-rc.13",
"@exodus/elliptic": "^6.5.4-precomputed",
"@exodus/key-identifier": "^1.1.2",
"@exodus/key-utils": "^3.5.1",
"@exodus/slip10": "^2.0.0",
"@exodus/sodium-crypto": "^3.1.0",
"bn.js": "^5.2.1",
"buffer-json": "^2.0.0",
"create-hash": "^1.2.0",
"json-stable-stringify": "^1.0.1",
"minimalistic-assert": "^1.0.1"
},
"devDependencies": {
"@exodus/key-ids": "^1.0.0",
"@noble/secp256k1": "^1.7.1",
"bip39": "2.6.0",
"bn.js": "^5.2.1",
"eslint": "^8.44.0",
"events": "^3.3.0"
},
Expand Down
2 changes: 0 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2555,7 +2555,6 @@ __metadata:
"@exodus/basic-utils": ^3.0.1
"@exodus/bip32": ^2.1.0
"@exodus/crypto": ^1.0.0-rc.13
"@exodus/elliptic": ^6.5.4-precomputed
"@exodus/key-identifier": ^1.1.2
"@exodus/key-ids": ^1.0.0
"@exodus/key-utils": ^3.5.1
Expand All @@ -2565,7 +2564,6 @@ __metadata:
bip39: 2.6.0
bn.js: ^5.2.1
buffer-json: ^2.0.0
create-hash: ^1.2.0
eslint: ^8.44.0
events: ^3.3.0
json-stable-stringify: ^1.0.1
Expand Down

0 comments on commit 292dd33

Please sign in to comment.