Skip to content
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
7 changes: 3 additions & 4 deletions src/chain-adapters/Bitcoin/Bitcoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
} from '@chain-adapters/Bitcoin/types'
import { parseBTCNetwork } from '@chain-adapters/Bitcoin/utils'
import { ChainAdapter } from '@chain-adapters/ChainAdapter'
import type { BaseChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { ChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { HashToSign, RSVSignature, UncompressedPubKeySEC1 } from '@types'
import { cryptography } from '@utils'

Expand All @@ -26,7 +26,7 @@ export class Bitcoin extends ChainAdapter<

private readonly network: BTCNetworkIds
private readonly btcRpcAdapter: BTCRpcAdapter
private readonly contract: BaseChainSignatureContract
private readonly contract: ChainSignatureContract

/**
* Creates a new Bitcoin chain instance
Expand All @@ -41,7 +41,7 @@ export class Bitcoin extends ChainAdapter<
btcRpcAdapter,
}: {
network: BTCNetworkIds
contract: BaseChainSignatureContract
contract: ChainSignatureContract
btcRpcAdapter: BTCRpcAdapter
}) {
super()
Expand Down Expand Up @@ -230,7 +230,6 @@ export class Bitcoin extends ChainAdapter<
const psbt = await this.createPSBT({
transactionRequest,
})

// We can't double sign a PSBT, therefore we serialize the payload before to return it
const psbtHex = psbt.toHex()

Expand Down
6 changes: 3 additions & 3 deletions src/chain-adapters/Cosmos/Cosmos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {
BalanceResponse,
} from '@chain-adapters/Cosmos/types'
import { fetchChainInfo } from '@chain-adapters/Cosmos/utils'
import type { BaseChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { ChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { HashToSign, RSVSignature, UncompressedPubKeySEC1 } from '@types'
import { cryptography } from '@utils'

Expand All @@ -37,7 +37,7 @@ export class Cosmos extends ChainAdapter<
> {
private readonly registry: Registry
private readonly chainId: CosmosNetworkIds
private readonly contract: BaseChainSignatureContract
private readonly contract: ChainSignatureContract
private readonly endpoints?: {
rpcUrl?: string
restUrl?: string
Expand All @@ -57,7 +57,7 @@ export class Cosmos extends ChainAdapter<
contract,
endpoints,
}: {
contract: BaseChainSignatureContract
contract: ChainSignatureContract
chainId: CosmosNetworkIds
endpoints?: {
rpcUrl?: string
Expand Down
18 changes: 9 additions & 9 deletions src/chain-adapters/EVM/EVM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type {
UserOperationV7,
} from '@chain-adapters/EVM/types'
import { fetchEVMFeeProperties } from '@chain-adapters/EVM/utils'
import type { BaseChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { ChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { HashToSign, RSVSignature } from '@types'

/**
Expand All @@ -42,7 +42,7 @@ export class EVM extends ChainAdapter<
EVMUnsignedTransaction
> {
private readonly client: PublicClient
private readonly contract: BaseChainSignatureContract
private readonly contract: ChainSignatureContract

/**
* Creates a new EVM chain instance
Expand All @@ -55,7 +55,7 @@ export class EVM extends ChainAdapter<
contract,
}: {
publicClient: PublicClient
contract: BaseChainSignatureContract
contract: ChainSignatureContract
}) {
super()

Expand Down Expand Up @@ -244,11 +244,11 @@ export class EVM extends ChainAdapter<
userOp.paymaster &&
isAddress(userOp.paymaster)
? concat([
userOp.paymaster,
pad(userOp.paymasterVerificationGasLimit, { size: 16 }),
pad(userOp.paymasterPostOpGasLimit, { size: 16 }),
userOp.paymasterData,
])
userOp.paymaster,
pad(userOp.paymasterVerificationGasLimit, { size: 16 }),
pad(userOp.paymasterPostOpGasLimit, { size: 16 }),
userOp.paymasterData,
])
: 'paymasterAndData' in userOp
? userOp.paymasterAndData
: '0x'
Expand Down Expand Up @@ -325,7 +325,7 @@ export class EVM extends ChainAdapter<
const hash = await this.client.sendRawTransaction({
serializedTransaction: txSerialized as `0x${string}`,
})
return { hash: hash }
return { hash }
} catch (error) {
console.error('Transaction broadcast failed:', error)
throw new Error('Failed to broadcast transaction.')
Expand Down
6 changes: 3 additions & 3 deletions src/chain-adapters/Solana/Solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Connection as SolanaConnection } from '@solana/web3.js'
import { PublicKey, Transaction, SystemProgram } from '@solana/web3.js'
import type BN from 'bn.js'

import type { BaseChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { ChainSignatureContract } from '@contracts/ChainSignatureContract'
import type { HashToSign, Signature } from '@types'

import { ChainAdapter } from '../ChainAdapter'
Expand All @@ -22,11 +22,11 @@ export class Solana extends ChainAdapter<
SolanaUnsignedTransaction
> {
private readonly connection: SolanaConnection
private readonly contract: BaseChainSignatureContract
private readonly contract: ChainSignatureContract

constructor(args: {
solanaConnection: SolanaConnection
contract: BaseChainSignatureContract
contract: ChainSignatureContract
}) {
super()
this.connection = args.solanaConnection
Expand Down
213 changes: 149 additions & 64 deletions src/contracts/ChainSignatureContract.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,158 @@
import type BN from 'bn.js'
import { type CodeResult } from '@near-js/types'
import { type Transaction } from '@near-wallet-selector/core'
import {
najToUncompressedPubKeySEC1,
uint8ArrayToHex,
} from '@utils/cryptography'
import { providers } from 'near-api-js'

import type {
RSVSignature,
UncompressedPubKeySEC1,
Ed25519PubKey,
DerivedPublicKeyArgs,
} from '../types'
import {
type RSVSignature,
type UncompressedPubKeySEC1,
type NajPublicKey,
type MPCSignature,
} from '@types'

export interface ArgsEd25519 extends DerivedPublicKeyArgs {
IsEd25519: boolean
import { NEAR_MAX_GAS } from './constants'
import { responseToMpcSignature } from './transaction'
import { type NearNetworkIds } from './types'

interface ViewMethodParams {
contractId: string
method: string
args?: Record<string, unknown>
}

export interface SignArgs {
/** The payload to sign as an array of 32 bytes */
payload: number[]
/** The derivation path for key generation */
export type HashToSign = number[]

export interface SignArgs<T = unknown> {
payloads: HashToSign[]
path: string
/** Version of the key to use */
key_version: number
keyType: 'Eddsa' | 'Ecdsa'
signerAccount: {
accountId: string
signAndSendTransactions: (transactions: {
transactions: Transaction[]
}) => Promise<T>
}
}

/**
* Base contract interface required for compatibility with ChainAdapter instances like EVM and Bitcoin.
*
* See {@link EVM} and {@link Bitcoin} for example implementations.
*/
export abstract class BaseChainSignatureContract {
/**
* Gets the current signature deposit required by the contract.
* This deposit amount helps manage network congestion.
*
* @returns Promise resolving to the required deposit amount as a BigNumber
*/
abstract getCurrentSignatureDeposit(): Promise<BN>

/**
* Gets the derived public key for a given path and predecessor.
*
* @param args - Arguments for key derivation
* @param args.path - The path to use derive the key
* @param args.predecessor - The id/address of the account requesting signature
* @param args.IsEd25519 - Flag indicating if the key is Ed25519
* @returns Promise resolving to the derived SEC1 uncompressed public key
*/
// abstract getDerivedPublicKey(args: ArgsEd25519): Promise<Ed25519PubKey>
abstract getDerivedPublicKey(
args: DerivedPublicKeyArgs | ArgsEd25519
): Promise<UncompressedPubKeySEC1 | Ed25519PubKey>
}
export class ChainSignatureContract {
private readonly contractId: string
private readonly networkId: NearNetworkIds
private readonly provider: providers.FailoverRpcProvider

constructor({
contractId,
networkId,
fallbackRpcUrls,
}: {
contractId: string
networkId: NearNetworkIds
fallbackRpcUrls?: string[]
}) {
this.contractId = contractId
this.networkId = networkId

const rpcProviderUrls =
fallbackRpcUrls && fallbackRpcUrls.length > 0
? fallbackRpcUrls
: [`https://rpc.${this.networkId}.near.org`]

this.provider = new providers.FailoverRpcProvider(
rpcProviderUrls.map((url) => new providers.JsonRpcProvider({ url }))
)
}

private async viewFunction({
contractId,
method,
args = {},
}: ViewMethodParams): Promise<number | string | object> {
const res = await this.provider.query<CodeResult>({
request_type: 'call_function',
account_id: contractId,
method_name: method,
args_base64: Buffer.from(JSON.stringify(args)).toString('base64'),
finality: 'optimistic',
})

return JSON.parse(Buffer.from(res.result).toString())
}

getCurrentSignatureDeposit(): number {
return 1
}

async sign({
payloads,
path,
keyType,
signerAccount,
}: SignArgs): Promise<RSVSignature[]> {
const transactions = payloads.map((payload) => ({
signerId: signerAccount.accountId,
receiverId: this.contractId,
actions: [
{
type: 'FunctionCall' as const,
params: {
methodName: 'sign',
args: {
request: {
payload_v2: { [keyType]: uint8ArrayToHex(payload) },
path,
domain_id: keyType === 'Eddsa' ? 1 : 0,
},
},
gas: NEAR_MAX_GAS.toString(),
deposit: '1',
},
},
],
}))

const sentTxs = (await signerAccount.signAndSendTransactions({
transactions,
})) as MPCSignature[]

const rsvSignatures = sentTxs.map((tx) =>
responseToMpcSignature({ signature: tx })
)

return rsvSignatures as RSVSignature[]
}

async getPublicKey(): Promise<UncompressedPubKeySEC1> {
const najPubKey = await this.viewFunction({
contractId: this.contractId,
method: 'public_key',
})
return najToUncompressedPubKeySEC1(najPubKey as NajPublicKey)
}

async getDerivedPublicKey(args: {
path: string
predecessor: string
IsEd25519?: boolean
}): Promise<UncompressedPubKeySEC1 | `Ed25519:${string}`> {
if (args.IsEd25519) {
return (await this.viewFunction({
contractId: this.contractId,
method: 'derived_public_key',
args: {
path: args.path,
predecessor: args.predecessor,
domain_id: 1,
},
})) as `Ed25519:${string}`
}

/**
* Full contract interface that extends BaseChainSignatureContract to provide all Sig Network Smart Contract capabilities.
*/
export abstract class ChainSignatureContract extends BaseChainSignatureContract {
/**
* Signs a payload using Sig Network MPC.
*
* @param args - Arguments for the signing operation
* @param args.payload - The data to sign as an array of 32 bytes
* @param args.path - The string path to use derive the key
* @param args.key_version - Version of the key to use
* @returns Promise resolving to the RSV signature
*/
abstract sign(args: SignArgs & Record<string, unknown>): Promise<RSVSignature>

/**
* Gets the public key associated with this contract instance.
*
* @returns Promise resolving to the SEC1 uncompressed public key
*/
abstract getPublicKey(): Promise<UncompressedPubKeySEC1>
const najPubKey = (await this.viewFunction({
contractId: this.contractId,
method: 'derived_public_key',
args,
})) as NajPublicKey
return najToUncompressedPubKeySEC1(najPubKey)
}
}
2 changes: 1 addition & 1 deletion src/contracts/near/account.ts → src/contracts/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Account, Connection } from '@near-js/accounts'
import { KeyPair } from '@near-js/crypto'
import { InMemoryKeyStore } from '@near-js/keystores'

import { DONT_CARE_ACCOUNT_ID } from '@contracts/near/constants'
import { DONT_CARE_ACCOUNT_ID } from '@contracts/constants'

type SetConnectionArgs =
| {
Expand Down
File renamed without changes.
Loading