Skip to content

Commit e6743ef

Browse files
committed
feat(signers): add Ecdsa
1 parent 94842db commit e6743ef

File tree

1 file changed

+70
-0
lines changed

1 file changed

+70
-0
lines changed

docs/pages/signers.md

+70
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,73 @@ const polkadotSigner = getPolkadotSigner(
119119
hdkdKeyPair.sign,
120120
)
121121
```
122+
123+
#### `Ecdsa`
124+
125+
Creating an `Ecdsa` `PolkadotSigner` can be tricky, especially when dealing with different chains like Polkadot and EVM-like chains. Below is some code to illustrate how this can be done effectively.
126+
127+
##### Chain Differences in Signers
128+
129+
- **EVM-like chains** (Moonbeam, Mythos, Darwinia, Crab, etc.) expect the signer to sign payloads using a **Keccak256 hash** and use **AccountId20** addresses (Ethereum-like addresses).
130+
- **Polkadot-like chains** (e.g., Polkadot, Kusama) expect the signer to sign payloads using **Blake2_256** and use **AccountId32** addresses (Polkadot-like addresses).
131+
132+
With that distinction in mind, here's how you can create `Ecdsa` `PolkadotSigner`s for these different chain types:
133+
134+
:::warning
135+
The following code is for illustrative purposes only. It stores private keys in memory, which is not ideal from a security standpoint. You should refactor the code to meet the security standards of your environment.
136+
:::
137+
138+
```ts
139+
import { mnemonicToSeedSync } from "@scure/bip39"
140+
import { HDKey } from "@scure/bip32"
141+
import { getPolkadotSigner, type PolkadotSigner } from "polkadot-api/signer"
142+
import { secp256k1 } from "@noble/curves/secp256k1"
143+
import { keccak_256 } from "@noble/hashes/sha3"
144+
import { blake2b256 } from "@noble/hashes/blake2b"
145+
146+
const signEcdsa = (
147+
hasher: (input: Uint8Array) => Uint8Array,
148+
value: Uint8Array,
149+
priv: Uint8Array,
150+
) => {
151+
const signature = secp256k1.sign(hasher(value), priv)
152+
const signedBytes = signature.toCompactRawBytes()
153+
154+
const result = new Uint8Array(signedBytes.length + 1)
155+
result.set(signedBytes)
156+
result[signedBytes.length] = signature.recovery
157+
158+
return result
159+
}
160+
161+
// A signer for EVM like chains that use AccountId20 as their public address
162+
const getEvmEcdsaSigner = (privateKey: Uint8Array): PolkadotSigner => {
163+
const publicAddress = keccak_256(
164+
secp256k1.getPublicKey(privateKey, false).slice(1),
165+
).slice(-20)
166+
167+
return getPolkadotSigner(publicAddress, "Ecdsa", (iput) =>
168+
signEcdsa(keccak_256, input, privateKey),
169+
)
170+
}
171+
172+
const getEvmEcdsaSignerFromMnemonic = (
173+
mnemonic: string,
174+
accountIdx: number = 0,
175+
password: string = "",
176+
): PolkadotSigner => {
177+
const seed = mnemonicToSeedSync(mnemonic, password)
178+
const keyPair = HDKey.fromMasterSeed(seed).derive(
179+
`m/44'/60'/0'/0/${accountIdx}`,
180+
)
181+
return getEvmEcdsaSigner(keyPair.privateKey!)
182+
}
183+
184+
// A signer for Polkadot like chains that use AccountId32 as the public address
185+
const getPolkadotEcdsaSigner = (privateKey: Uint8Array): PolkadotSigner =>
186+
getPolkadotSigner(
187+
blake2b(secp256k1.getPublicKey(privateKey), { dkLen: 32 }),
188+
"Ecdsa",
189+
(input) => signEcdsa((i) => blake2b(i, { dkLen: 32 }), input, privateKey),
190+
)
191+
```

0 commit comments

Comments
 (0)