Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dapp-connector): Add CIP-30 signData support #3280

Merged
merged 18 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/wallet-mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ PODS:
- React-Core
- react-native-haskell-shelley (6.0.0-alpha.9):
- React
- react-native-message_signing-library (1.0.2):
- React
- react-native-mmkv (2.11.0):
- MMKV (>= 1.2.13)
- React-Core
Expand Down Expand Up @@ -588,6 +590,7 @@ DEPENDENCIES:
- react-native-ble-plx (from `../../../node_modules/react-native-ble-plx`)
- react-native-config (from `../../../node_modules/react-native-config`)
- "react-native-haskell-shelley (from `../../../node_modules/@emurgo/csl-mobile-bridge`)"
- "react-native-message_signing-library (from `../../../node_modules/@emurgo/msl-mobile-bridge`)"
- react-native-mmkv (from `../../../node_modules/react-native-mmkv`)
- react-native-pager-view (from `../../../node_modules/react-native-pager-view`)
- react-native-quick-base64 (from `../../../node_modules/react-native-quick-base64`)
Expand Down Expand Up @@ -738,6 +741,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-config"
react-native-haskell-shelley:
:path: "../../../node_modules/@emurgo/csl-mobile-bridge"
react-native-message_signing-library:
:path: "../../../node_modules/@emurgo/msl-mobile-bridge"
react-native-mmkv:
:path: "../../../node_modules/react-native-mmkv"
react-native-pager-view:
Expand Down Expand Up @@ -872,6 +877,7 @@ SPEC CHECKSUMS:
react-native-ble-plx: f10240444452dfb2d2a13a0e4f58d7783e92d76e
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
react-native-haskell-shelley: 4015aef14eca3ecf5e8d8718e8d1b47cdcf60f84
react-native-message_signing-library: e81294a3cd40d74d3d83f0a1de72aa319238f09a
react-native-mmkv: e97c0c79403fb94577e5d902ab1ebd42b0715b43
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
react-native-quick-base64: 62290829c619fbabca4c41cfec75ae759d08fc1c
Expand Down
4 changes: 3 additions & 1 deletion apps/wallet-mobile/jestSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ jest.mock('react-native-ble-plx', () => ({}))
jest.mock('@react-native-async-storage/async-storage', () =>
require('@react-native-async-storage/async-storage/jest/async-storage-mock'),
)

jest.mock('@emurgo/cross-msl-mobile', () => require('@emurgo/cross-msl-nodejs'))

jest.mock('react-native-keychain', () => ({
resetGenericPassword: jest.fn(),
}))
Expand Down Expand Up @@ -57,4 +60,3 @@ jest.mock('react-native-localize', () => ({
usesAutoDateAndTime: () => true,
usesAutoTimeZone: () => true,
}))

3 changes: 3 additions & 0 deletions apps/wallet-mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@
"@emurgo/cip4-js": "1.0.7",
"@emurgo/cross-csl-core": "4.4.0",
"@emurgo/cross-csl-mobile": "4.4.0",
"@emurgo/cross-msl-mobile": "^1.0.0",
"@emurgo/csl-mobile-bridge": "6.0.0-alpha.9",
"@emurgo/msl-mobile-bridge": "^1.0.2",
"@emurgo/react-native-blockies-svg": "^0.0.2",
"@emurgo/react-native-hid": "^5.15.6",
"@emurgo/yoroi-lib": "0.15.4-alpha.7",
Expand Down Expand Up @@ -217,6 +219,7 @@
"@babel/runtime": "^7.20.0",
"@emurgo/cardano-serialization-lib-nodejs": "12.0.0-alpha.28",
"@emurgo/cross-csl-nodejs": "4.4.0",
"@emurgo/cross-msl-nodejs": "^1.0.0",
"@formatjs/cli": "^6.1.0",
"@formatjs/ts-transformer": "^3.13.0",
"@react-navigation/devtools": "^6.0.13",
Expand Down
2 changes: 1 addition & 1 deletion apps/wallet-mobile/src/features/Discover/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {connectionStorageMaker, dappConnectorApiMaker, dappConnectorMaker, Resol
import {DappConnector} from '@yoroi/dapp-connector'
import {App} from '@yoroi/types'

import {cip30ExtensionMaker} from '../../../yoroi-wallets/cardano/cip30'
import {cip30ExtensionMaker} from '../../../yoroi-wallets/cardano/cip30/cip30'
import {YoroiWallet} from '../../../yoroi-wallets/cardano/types'

export const validUrl = (url: string) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {mocks} from '../mocks'
import {getMasterKeyFromMnemonic} from './byron/util'
import {mocks} from '../../mocks'
import {getMasterKeyFromMnemonic} from '../byron/util'
import {YoroiWallet} from '../types'
import {cip30ExtensionMaker} from './cip30'
import {YoroiWallet} from './types'

describe('cip30ExtensionMaker', () => {
it('should support submitTx', async () => {
Expand Down Expand Up @@ -76,9 +76,26 @@ describe('cip30ExtensionMaker', () => {

it('should support signTx', async () => {
const cip30 = cip30ExtensionMaker(mocks.wallet)
const result = await cip30.signTx(await getMasterKeyFromMnemonic(mnemonic), txCbor, true)
const rootKey = await getMasterKeyFromMnemonic(mnemonic)
const result = await cip30.signTx(rootKey, txCbor, true)
expect(result).toBeDefined()
})

it('should support signData', async () => {
const rootKey = await getMasterKeyFromMnemonic(mnemonic)
const message = 'whatever'
const addressBech32 =
'addr1qynqc23tpx4dqps6xgqy9s2l3xz5fxu734wwmzj9uddn0h2z6epfcukqmswgwwfruxh7gaddv9x0d5awccwahnhwleqqc4zkh4'

const cip30 = cip30ExtensionMaker(mocks.wallet)
const result = await cip30.signData(rootKey, addressBech32, message)

expect(result).toEqual({
signature:
'845846a201276761646472657373583901260c2a2b09aad0061a320042c15f8985449b9e8d5ced8a45e35b37dd42d6429c72c0dc1c873923e1afe475ad614cf6d3aec61ddbceeefe40a166686173686564f448776861746576657258406a16fcb9cc6c3f7d83fdd623e8896d4b81c0ef6a9fb68d916794e2e4c3c0766666b485f71c6f1f56241cb30905cc18618c7e95721dba3e91bcd9918f51e8b90a',
key: 'a4010103272006215820be6a4d7e9789dd7458049c971ad783107e7f041c6f1f542b4530d63d5fe5afb1',
})
})
})

const mnemonic = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as CSL from '@emurgo/cross-csl-core'
import {WasmModuleProxy} from '@emurgo/cross-csl-core'
import {init} from '@emurgo/cross-msl-mobile'
import {RemoteUnspentOutput, signRawTransaction, UtxoAsset} from '@emurgo/yoroi-lib'
import {normalizeToAddress} from '@emurgo/yoroi-lib/dist/internals/utils/addresses'
import {parseTokenList} from '@emurgo/yoroi-lib/dist/internals/utils/assets'
Expand All @@ -9,22 +10,23 @@ import {BigNumber} from 'bignumber.js'
import {Buffer} from 'buffer'
import _ from 'lodash'

import {RawUtxo, YoroiSignedTx, YoroiUnsignedTx} from '../types'
import {asQuantity, Utxos} from '../utils'
import {Cardano, CardanoMobile} from '../wallets'
import {toAssetNameHex, toPolicyId} from './api'
import {getTransactionSigners} from './common/signatureUtils'
import {Pagination, YoroiWallet} from './types'
import {createRawTxSigningKey, identifierToCardanoAsset} from './utils'
import {collateralConfig, findCollateralCandidates, utxosMaker} from './utxoManager/utxos'
import {wrappedCsl} from './wrappedCsl'
import {RawUtxo, YoroiSignedTx, YoroiUnsignedTx} from '../../types'
import {asQuantity, Utxos} from '../../utils'
import {Cardano, CardanoMobile} from '../../wallets'
import {toAssetNameHex, toPolicyId} from '../api'
import * as cip8 from '../cip8/cip8'
import {getDerivationPathForAddress, getTransactionSigners} from '../common/signatureUtils'
import {Pagination, YoroiWallet} from '../types'
import {createRawTxSigningKey, identifierToCardanoAsset} from '../utils'
import {collateralConfig, findCollateralCandidates, utxosMaker} from '../utxoManager/utxos'
import {wrappedCsl as getCSL} from '../wrappedCsl'

const MSL = init('msl')

export const cip30ExtensionMaker = (wallet: YoroiWallet) => {
return new CIP30Extension(wallet)
}

const getCSL = () => wrappedCsl()

const copy = async <T extends {toHex: () => Promise<string>}>(
creator: {fromHex: (hex: string) => Promise<T>},
value: T,
Expand Down Expand Up @@ -129,13 +131,33 @@ class CIP30Extension {
return txId
}

async signData(_rootKey: string, address: string, _payload: string): Promise<{signature: string; key: string}> {
async signData(rootKey: string, address: string, payload: string): Promise<{signature: string; key: string}> {
const {csl, release} = getCSL()
try {
const payloadInBytes = Buffer.from(payload, 'utf-8')

const normalisedAddress = await normalizeToAddress(csl, address)
const bech32Address = await normalisedAddress?.toBech32(undefined)
if (!bech32Address) throw new Error('Invalid wallet state')
throw new Error('Not implemented')
if (!bech32Address || !normalisedAddress) throw new Error('Invalid address')

const path = getDerivationPathForAddress(bech32Address, this.wallet, true)
const signingKey = await createRawTxSigningKey(rootKey, path)
const coseSign1 = await cip8.sign(Buffer.from(await normalisedAddress.toHex(), 'hex'), signingKey, payloadInBytes)
const key = await MSL.COSEKey.new(await MSL.Label.fromKeyType(MSL.KeyType.OKP))
await key.setAlgorithmId(await MSL.Label.fromAlgorithmId(MSL.AlgorithmId.EdDSA))
await key.setHeader(
await MSL.Label.newInt(await MSL.Int.newNegative(await MSL.BigNum.fromStr('1'))),
await MSL.CBORValue.newInt(await MSL.Int.newI32(6)),
)
await key.setHeader(
await MSL.Label.newInt(await MSL.Int.newNegative(await MSL.BigNum.fromStr('2'))),
await MSL.CBORValue.newBytes(await (await signingKey.toPublic()).asBytes()),
)

return {
signature: Buffer.from(await coseSign1.toBytes()).toString('hex'),
key: Buffer.from(await key.toBytes()).toString('hex'),
}
} finally {
release()
}
Expand Down
36 changes: 36 additions & 0 deletions apps/wallet-mobile/src/yoroi-wallets/cardano/cip8/cip8.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {normalizeToAddress} from '@emurgo/yoroi-lib/dist/internals/utils/addresses'
import assert from 'assert'
import {Buffer} from 'buffer'

import {getMasterKeyFromMnemonic} from '../byron/util'
import {harden} from '../common/signatureUtils'
import {createRawTxSigningKey} from '../utils'
import {wrappedCsl} from '../wrappedCsl'
import * as cip8 from './cip8'

describe('CIP8', () => {
it('should support signing', async () => {
const {csl} = wrappedCsl()
const bech32 =
'addr1qynqc23tpx4dqps6xgqy9s2l3xz5fxu734wwmzj9uddn0h2z6epfcukqmswgwwfruxh7gaddv9x0d5awccwahnhwleqqc4zkh4'
const payload = 'whatever'
const rootKey = await getMasterKeyFromMnemonic(mnemonic)
const path = [harden(1852), harden(1815), harden(0), 0, 0]
const signingKey = await createRawTxSigningKey(rootKey, path)

const payloadInBytes = Buffer.from(payload, 'utf-8')
const normalisedAddress = await normalizeToAddress(csl, bech32)
assert(normalisedAddress != null)
const coseSign1 = await cip8.sign(Buffer.from(await normalisedAddress.toHex(), 'hex'), signingKey, payloadInBytes)
const signature = Buffer.from(await coseSign1.toBytes()).toString('hex')
expect(signature).toEqual(
'845846a201276761646472657373583901260c2a2b09aad0061a320042c15f8985449b9e8d5ced8a45e35b37dd42d6429c72c0dc1c873923e1afe475ad614cf6d3aec61ddbceeefe40a166686173686564f448776861746576657258406a16fcb9cc6c3f7d83fdd623e8896d4b81c0ef6a9fb68d916794e2e4c3c0766666b485f71c6f1f56241cb30905cc18618c7e95721dba3e91bcd9918f51e8b90a',
)
})
})

const mnemonic = [
'dry balcony arctic what garbage sort',
'cart shine egg lamp manual bottom',
'slide assault bus',
].join(' ')
18 changes: 18 additions & 0 deletions apps/wallet-mobile/src/yoroi-wallets/cardano/cip8/cip8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {PrivateKey} from '@emurgo/cross-csl-core'
import {init} from '@emurgo/cross-msl-mobile'
import {Buffer} from 'buffer'

const MSL = init('cip8')

export const sign = async (address: Buffer, signKey: PrivateKey, payload: Buffer) => {
const protectedHeader = await MSL.HeaderMap.new()
await protectedHeader.setAlgorithmId(await MSL.Label.fromAlgorithmId(MSL.AlgorithmId.EdDSA))
await protectedHeader.setHeader(await MSL.Label.newText('address'), await MSL.CBORValue.newBytes(address))
const protectedSerialized = await MSL.ProtectedHeaderMap.new(protectedHeader)
const unprotected = await MSL.HeaderMap.new()
const headers = await MSL.Headers.new(protectedSerialized, unprotected)
const builder = await MSL.COSESign1Builder.new(headers, payload, false)
const toSign = await (await builder.makeDataToSign()).toBytes()
const signedSigStruct = await (await signKey.sign(toSign)).toBytes()
return builder.build(signedSigStruct)
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const arePathsEqual = (path1: number[], path2: number[]) => {
return path1.every((value, index) => value === path2[index]) && path1.length === path2.length
}

const getDerivationPathForAddress = (address: string, wallet: YoroiWallet, partial = false) => {
export const getDerivationPathForAddress = (address: string, wallet: YoroiWallet, partial = false) => {
const internalIndex = wallet.internalAddresses.indexOf(address)
const externalIndex = wallet.externalAddresses.indexOf(address)
const purpose = isHaskellShelley(wallet.walletImplementationId)
Expand Down
49 changes: 49 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2054,6 +2054,11 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==

"@emurgo/[email protected]":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@emurgo/cardano-message-signing-nodejs/-/cardano-message-signing-nodejs-1.0.1.tgz#b2fa1f7541055a6c4b8e805492b1a9362bea5835"
integrity sha512-PoKh1tQnJX18f8iEr8Jk1KXxKCn9eqaSslMI1pyOJvYRJhQVDLCh0+9YReufjp0oFJIY1ShcrR+4/WnECVZUKQ==

"@emurgo/[email protected]":
version "12.0.0-alpha.26"
resolved "https://registry.yarnpkg.com/@emurgo/cardano-serialization-lib-nodejs/-/cardano-serialization-lib-nodejs-12.0.0-alpha.26.tgz#4e5fea7ba6d72888e21c5a9bea6bcff367834a4b"
Expand Down Expand Up @@ -2111,6 +2116,36 @@
"@emurgo/cardano-serialization-lib-nodejs" "12.0.0-alpha.26"
"@emurgo/cross-csl-core" "4.4.0"

"@emurgo/[email protected]":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emurgo/cross-msl-core/-/cross-msl-core-1.0.0.tgz#f66ec9491362e9d60c7c6142875af849ca788c98"
integrity sha512-X98tmVLYSpcLggky3R7iuvb9no/LdoPCR8YXH/CjVj4U5YoR9/6Xf2PQOxSQCBCeExHZ7fDo+gefwoSX6yRjow==
dependencies:
"@cardano-foundation/ledgerjs-hw-app-cardano" "^5.0.0"
"@types/mocha" "^9.1.1"
axios "^0.24.0"
bech32 "^2.0.0"
bignumber.js "^9.0.1"
blake2b "^2.1.4"
hash-wasm "^4.9.0"
mocha "^10.0.0"

"@emurgo/cross-msl-mobile@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emurgo/cross-msl-mobile/-/cross-msl-mobile-1.0.0.tgz#677434190af90ed43fac46dd6c4c331ec0a0bcce"
integrity sha512-pHMc6P0TjskUAmeI52BId+w253M0EceolnN2m8xdFdqMw4FmrrdpLbfP4uqACHWiM3Vy419SzSczv1r/BHqCNw==
dependencies:
"@emurgo/cross-msl-core" "1.0.0"
"@emurgo/msl-mobile-bridge" "1.0.1"

"@emurgo/cross-msl-nodejs@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emurgo/cross-msl-nodejs/-/cross-msl-nodejs-1.0.0.tgz#3079042d10c9b4d723fe7e77fc9c85559e8c3ee5"
integrity sha512-M2qxS8fo6SSddUXSjnjDNQY1Of/Ro+3a2HUZhTcvN/RRa3JLKU36i3YUqSpMhTxHN0GwCM6QCE7LddpZthpJeA==
dependencies:
"@emurgo/cardano-message-signing-nodejs" "1.0.1"
"@emurgo/cross-msl-core" "1.0.0"

"@emurgo/[email protected]":
version "6.0.0-alpha.7"
resolved "https://registry.yarnpkg.com/@emurgo/csl-mobile-bridge/-/csl-mobile-bridge-6.0.0-alpha.7.tgz#63349d0f2e70d568ce628849c8d8b085892531aa"
Expand All @@ -2125,6 +2160,20 @@
dependencies:
base-64 "0.1.0"

"@emurgo/[email protected]":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@emurgo/msl-mobile-bridge/-/msl-mobile-bridge-1.0.1.tgz#4a912e182118dc4462e90cbfda5ebedf6048b4e6"
integrity sha512-ed0KobdbEmNParq33pTkYOMWrF8kMP6R3HdIPDy/YycwW9nvvWlWhlpUR4wKDjM3qQzhE/rN6gb/dG8rwWa4IQ==
dependencies:
base-64 "0.1.0"

"@emurgo/msl-mobile-bridge@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@emurgo/msl-mobile-bridge/-/msl-mobile-bridge-1.0.2.tgz#3241ff5728a7b34b890641843b36c62b6876d09f"
integrity sha512-RQ+X8ovF09jPr3xwG1XVAr/O2YFVZFDGALbLPMEjszvSw12Su3FNgLGvMwXfSvL9T/csR0BF1NqkGauJONedmw==
dependencies:
base-64 "0.1.0"

"@emurgo/react-native-blockies-svg@^0.0.2":
version "0.0.2"
resolved "https://registry.yarnpkg.com/@emurgo/react-native-blockies-svg/-/react-native-blockies-svg-0.0.2.tgz#afe2ce2776b0c6430ce3bc9a94ceb4f9fcb302f9"
Expand Down
Loading