diff --git a/src/client/NUT11.ts b/src/client/NUT11.ts index 23ee3d2..9bdfccd 100644 --- a/src/client/NUT11.ts +++ b/src/client/NUT11.ts @@ -4,6 +4,8 @@ import { schnorr } from '@noble/curves/secp256k1'; import { randomBytes } from '@noble/hashes/utils'; import { parseSecret } from '../common/NUT11.js'; import { Proof, Secret } from '../common/index.js'; +import { BlindedMessage, serializeBlindedMessage } from './index.js'; +import { ProjPointType } from '@noble/curves/abstract/weierstrass.js'; export const createP2PKsecret = (pubkey: string): Uint8Array => { const newSecret: Secret = [ @@ -23,6 +25,12 @@ export const signP2PKsecret = (secret: Uint8Array, privateKey: PrivKey) => { return sig; }; +export const signBlindedMessage = (B_: string, privateKey: PrivKey): Uint8Array => { + const msgHash = sha256(B_); + const sig = schnorr.sign(msgHash, privateKey); + return sig; +}; + export const getSignedProofs = (proofs: Array, privateKey: string): Array => { return proofs.map((p) => { try { @@ -37,6 +45,20 @@ export const getSignedProofs = (proofs: Array, privateKey: string): Array }); }; +export const getSignedOutput = (output: BlindedMessage, privateKey: PrivKey): BlindedMessage => { + const B_ = output.B_.toHex(true); + const signature = signBlindedMessage(B_, privateKey); + output.witness = { signatures: [bytesToHex(signature)] }; + return output; +}; + +export const getSignedOutputs = ( + outputs: Array, + privateKey: string +): Array => { + return outputs.map((o) => getSignedOutput(o, privateKey)); +}; + export const getSignedProof = (proof: Proof, privateKey: PrivKey): Proof => { if (!proof.witness) { proof.witness = { diff --git a/src/client/index.ts b/src/client/index.ts index acf1f93..3aa6cef 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -10,24 +10,34 @@ import type { } from '../common/index.js'; import { hashToCurve, pointFromHex } from '../common/index.js'; import { Witness } from '../common/index'; +import { PrivKey } from '@noble/curves/abstract/utils.js'; +import { getSignedOutput } from './NUT11.js'; export type BlindedMessage = { B_: ProjPointType; r: bigint; secret: Uint8Array; + witness?: Witness; }; -export function createRandomBlindedMessage(): BlindedMessage { - return blindMessage(randomBytes(32)); +export function createRandomBlindedMessage(privateKey?: PrivKey): BlindedMessage { + return blindMessage( + randomBytes(32), + bytesToNumber(secp256k1.utils.randomPrivateKey()), + privateKey + ); } -export function blindMessage(secret: Uint8Array, r?: bigint): BlindedMessage { +export function blindMessage(secret: Uint8Array, r?: bigint, privateKey?: PrivKey): BlindedMessage { const Y = hashToCurve(secret); if (!r) { r = bytesToNumber(secp256k1.utils.randomPrivateKey()); } const rG = secp256k1.ProjectivePoint.BASE.multiply(r); const B_ = Y.add(rG); + if (privateKey !== undefined) { + return getSignedOutput({ B_, r, secret }, privateKey); + } return { B_, r, secret }; } @@ -73,7 +83,7 @@ export const deserializeProof = (proof: SerializedProof): Proof => { C: pointFromHex(proof.C), id: proof.id, secret: new TextEncoder().encode(proof.secret), - witness: proof.witness?JSON.parse(proof.witness):undefined + witness: proof.witness ? JSON.parse(proof.witness) : undefined }; }; export const serializeBlindedMessage = ( diff --git a/src/common/NUT11.ts b/src/common/NUT11.ts index 57474ce..0660ef9 100644 --- a/src/common/NUT11.ts +++ b/src/common/NUT11.ts @@ -1,4 +1,4 @@ -import { Secret } from "./index.js"; +import { Secret } from './index.js'; export const parseSecret = (secret: string | Uint8Array): Secret => { try { diff --git a/src/mint/NUT11.ts b/src/mint/NUT11.ts index a9abd75..651c9e1 100644 --- a/src/mint/NUT11.ts +++ b/src/mint/NUT11.ts @@ -2,6 +2,8 @@ import { schnorr } from '@noble/curves/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; import { parseSecret } from '../common/NUT11.js'; import { Proof } from '../common/index.js'; +import { BlindedMessage } from '../client/index.js'; +import { ProjPointType } from '@noble/curves/abstract/weierstrass.js'; export const verifyP2PKSig = (proof: Proof) => { if (!proof.witness) { @@ -42,3 +44,14 @@ export const verifyP2PKSig = (proof: Proof) => { parsedSecret[1].data ); }; + +export const verifyP2PKSigOutput = (output: BlindedMessage, publicKey: string): boolean => { + if (!output.witness) { + throw new Error('could not verify signature, no witness provided'); + } + return schnorr.verify( + output.witness.signatures[0], + sha256(output.B_.toHex(true)), + publicKey + ) +} \ No newline at end of file diff --git a/test/client/NUT11.test.ts b/test/client/NUT11.test.ts index ff69990..debdf5d 100644 --- a/test/client/NUT11.test.ts +++ b/test/client/NUT11.test.ts @@ -3,7 +3,8 @@ import { createP2PKsecret, getSignedProof } from '../../src/client/NUT11.js'; import { bytesToHex } from '@noble/curves/abstract/utils'; import { Proof, pointFromHex } from '../../src/common'; import { parseSecret } from '../../src/common/NUT11.js'; -import { verifyP2PKSig } from '../../src/mint/NUT11.js'; +import { verifyP2PKSig, verifyP2PKSigOutput } from '../../src/mint/NUT11.js'; +import { createRandomBlindedMessage } from '../../src/client/index.js'; const PRIVKEY = schnorr.utils.randomPrivateKey(); const PUBKEY = schnorr.getPublicKey(PRIVKEY); @@ -32,4 +33,9 @@ describe('test create p2pk secret', () => { const verify = verifyP2PKSig(signedProof); expect(verify).toBe(true); }); + test('sign and verify blindedMessage', async() => { + const blindedMessage = createRandomBlindedMessage(PRIVKEY); + const verify = verifyP2PKSigOutput(blindedMessage, bytesToHex(PUBKEY)); + expect(verify).toBe(true); + }); });