diff --git a/packages/taquito-michel-codec/src/taquito-michel-codec.ts b/packages/taquito-michel-codec/src/taquito-michel-codec.ts index 6da2a22e83..4fac0c0bd0 100644 --- a/packages/taquito-michel-codec/src/taquito-michel-codec.ts +++ b/packages/taquito-michel-codec/src/taquito-michel-codec.ts @@ -11,6 +11,7 @@ export * from './michelson-typecheck'; export * from './michelson-contract'; export * from './formatters'; export * from './binary'; -export { MichelsonError, isMichelsonError, MichelsonTypeError } from './utils'; +export * from './base58'; +export { MichelsonError, isMichelsonError, MichelsonTypeError, checkDecodeTezosID, encodeTezosID, TezosIDType } from './utils'; export { MacroError } from './macros'; export { VERSION } from './version'; diff --git a/packages/taquito-remote-signer/README.md b/packages/taquito-remote-signer/README.md index 6f24142b9c..6610fc5820 100644 --- a/packages/taquito-remote-signer/README.md +++ b/packages/taquito-remote-signer/README.md @@ -32,6 +32,16 @@ await Tezos.contract.transfer({ to: publicKeyHash, amount: 2 }); The constructor of the `RemoteSigner` class requires the public key hash and the URL of the remote signer as parameters. It also takes optional headers (i.e., Authorization) and an optional `HttpBackend` to override the default one if needed. +### Authenticated requests +`RemoteSigner` can use an authenticated protocol. All you need is to pass a secret key in the Tezos Base58 format as `RemoteSignerOptions` property: + +```ts +const authSecretKey = 'edsk39CjLvKZhJ1NEaCTE6A1cgHjt5mK2mFdCqqMr1P54qLnwmiCAr'; +// ... +const signer = new RemoteSigner(pkh, rootUrl, { authSecretKey }); +// next steps as above +``` + ## Additional info See the top-level [https://github.com/ecadlabs/taquito](https://github.com/ecadlabs/taquito) file for details on reporting issues, contributing, and versioning. diff --git a/packages/taquito-remote-signer/package.json b/packages/taquito-remote-signer/package.json index 199ddbafcd..6356b2d01f 100644 --- a/packages/taquito-remote-signer/package.json +++ b/packages/taquito-remote-signer/package.json @@ -66,13 +66,16 @@ "@taquito/http-utils": "^14.0.0", "@taquito/taquito": "^14.0.0", "@taquito/utils": "^14.0.0", - "typedarray-to-buffer": "^4.0.0" + "@taquito/michel-codec": "^14.0.0", + "typedarray-to-buffer": "^4.0.0", + "elliptic": "^6.5.4" }, "devDependencies": { "@types/bluebird": "^3.5.36", "@types/jest": "^26.0.24", "@types/node": "^17.0.0", "@types/ws": "^8.2.2", + "@types/elliptic": "^6.4.14", "@typescript-eslint/eslint-plugin": "^5.28.0", "@typescript-eslint/parser": "^5.28.0", "colors": "^1.4.0", diff --git a/packages/taquito-remote-signer/src/auth.ts b/packages/taquito-remote-signer/src/auth.ts new file mode 100644 index 0000000000..69b1786405 --- /dev/null +++ b/packages/taquito-remote-signer/src/auth.ts @@ -0,0 +1,54 @@ +import { checkDecodeTezosID, encodeTezosID } from '@taquito/michel-codec'; +import * as ed25519 from '@stablelib/ed25519'; +import * as blake2b from '@stablelib/blake2b'; +import elliptic from 'elliptic'; +import { TezosIDType } from '@taquito/michel-codec'; + +enum PublicKeyHashID { + ED25519 = 0, + SECP256K1 = 1, + P256 = 2, +} + +function computeDigest(msg: Uint8Array, pkh: [TezosIDType, number[]]): Uint8Array { + const hashType = pkh[0] == 'ED25519PublicKeyHash' ? PublicKeyHashID.ED25519 : + pkh[0] == 'SECP256K1PublicKeyHash' ? PublicKeyHashID.SECP256K1 : + PublicKeyHashID.P256; + + const buf = new Uint8Array(msg.length + pkh[1].length + 3); + buf.set([4, 1, hashType]); + buf.set(pkh[1], 3); + buf.set(msg, 3+pkh[1].length); + return blake2b.hash(buf, 32); +} + +export function authenticateRequest(msg: Uint8Array, secretKey: string, pkh: string): string { + const tmp = checkDecodeTezosID(secretKey, 'ED25519Seed', 'P256SecretKey', 'SECP256K1SecretKey'); + if (tmp == null) { + throw new Error('invalid private key format'); + } + const [t, secret] = tmp; + const pubHash = checkDecodeTezosID(pkh, 'ED25519PublicKeyHash', 'P256PublicKeyHash', 'SECP256K1PublicKeyHash'); + if (pubHash == null) { + throw new Error('invalid public key hash format'); + } + const secData = new Uint8Array(secret); + const digest = computeDigest(msg, pubHash); + let signature: Uint8Array; + let sigType: TezosIDType; + if (t == 'ED25519Seed') { + const kp = ed25519.generateKeyPairFromSeed(secData); + signature = ed25519.sign(kp.secretKey, digest); + sigType = 'ED25519Signature'; + } else { + const kp = new elliptic.ec(t == 'SECP256K1SecretKey' ? 'secp256k1' : 'p256').keyFromPrivate(secData); + const sig = kp.sign(digest); + const r = sig.r.toArray(); + const s = sig.s.toArray(); + signature = new Uint8Array(64); + signature.set(r, 32-r.length); + signature.set(s, 64-s.length); + sigType = t == 'SECP256K1SecretKey' ? 'SECP256K1Signature' : 'P256Signature'; + } + return encodeTezosID(sigType, signature); +} \ No newline at end of file diff --git a/packages/taquito-remote-signer/src/taquito-remote-signer.ts b/packages/taquito-remote-signer/src/taquito-remote-signer.ts index caca161802..4b9ebd8e08 100644 --- a/packages/taquito-remote-signer/src/taquito-remote-signer.ts +++ b/packages/taquito-remote-signer/src/taquito-remote-signer.ts @@ -26,6 +26,7 @@ import { OperationNotAuthorizedError, PublicKeyMismatch, } from './errors'; +import { authenticateRequest } from './auth'; import { Signer } from '@taquito/taquito'; /** @@ -59,6 +60,7 @@ type curves = 'ed' | 'p2' | 'sp'; export interface RemoteSignerOptions { headers?: { [key: string]: string }; + authSecretKey?: string; } export { VERSION } from './version'; @@ -134,11 +136,15 @@ export class RemoteSigner implements Signer { bb = mergebuf(watermark, bb); } const watermarkedBytes = buf2hex(toBuffer(bb)); + const auth = this.options.authSecretKey !== undefined ? + authenticateRequest(bb, this.options.authSecretKey, this.pkh) : + null; const { signature } = await this.http.createRequest( { url: this.createURL(`/keys/${this.pkh}`), method: 'POST', headers: this.options.headers, + ...(auth !== null ? { query: { authentication: auth } } : undefined) }, watermarkedBytes );