@bsv/sdk
Version:
BSV Blockchain Software Development Kit
94 lines (90 loc) • 3.45 kB
text/typescript
// @ts-nocheck
import PublicKey from '../primitives/PublicKey.js'
import PrivateKey from '../primitives/PrivateKey.js'
import Signature from '../primitives/Signature.js'
import Curve from '../primitives/Curve.js'
import Random from '../primitives/Random.js'
import { toBase64, toArray, Reader, toHex } from '../primitives/utils.js'
const VERSION = '42423301'
/**
* Signs a message from one party to be verified by another, or for verification by anyone, using the BRC-77 message signing protocol.
* @param message The message to sign
* @param signer The private key of the message signer
* @param [verifier] The public key of the person who can verify the message. If not provided, anyone will be able to verify the message signature.
*
* @returns The message signature.
*/
export const sign = (
message: number[],
signer: PrivateKey,
verifier?: PublicKey
): number[] => {
const recipientAnyone = typeof verifier !== 'object'
if (recipientAnyone) {
const curve = new Curve()
const anyone = new PrivateKey(1)
const anyonePoint = curve.g.mul(anyone)
verifier = new PublicKey(anyonePoint.x, anyonePoint.y)
}
const keyID = Random(32)
const keyIDBase64 = toBase64(keyID)
const invoiceNumber = `2-message signing-${keyIDBase64}`
const signingKey = signer.deriveChild(verifier, invoiceNumber)
const signature = signingKey.sign(message).toDER()
const senderPublicKey = signer.toPublicKey().encode(true)
const version = toArray(VERSION, 'hex')
return [
...version,
...senderPublicKey,
...(recipientAnyone ? [0] : verifier.encode(true)),
...keyID,
...signature
]
}
/**
* Verifies a message using the BRC-77 message signing protocol.
* @param message The message to verify.
* @param sig The message signature to be verified.
* @param [recipient] The private key of the message verifier. This can be omitted if the message is verifiable by anyone.
*
* @returns True if the message is verified.
*/
export const verify = (
message: number[],
sig: number[],
recipient?: PrivateKey
): boolean => {
const reader = new Reader(sig)
const messageVersion = toHex(reader.read(4))
if (messageVersion !== VERSION) {
throw new Error(
`Message version mismatch: Expected ${VERSION}, received ${messageVersion}`
)
}
const signer = PublicKey.fromString(toHex(reader.read(33)))
const [verifierFirst] = reader.read(1)
if (verifierFirst === 0) {
recipient = new PrivateKey(1)
} else {
const verifierRest = reader.read(32)
const verifierDER = toHex([verifierFirst, ...verifierRest])
if (typeof recipient !== 'object') {
throw new Error(
`This signature can only be verified with knowledge of a specific private key. The associated public key is: ${verifierDER}`
)
}
const recipientDER = recipient.toPublicKey().encode(true, 'hex') as string
if (verifierDER !== recipientDER) {
throw new Error(
`The recipient public key is ${recipientDER} but the signature requres the recipient to have public key ${verifierDER}`
)
}
}
const keyID = toBase64(reader.read(32))
const signatureDER = toHex(reader.read(reader.bin.length - reader.pos))
const signature = Signature.fromDER(signatureDER, 'hex')
const invoiceNumber = `2-message signing-${keyID}`
const signingKey = signer.deriveChild(recipient, invoiceNumber)
const verified = signingKey.verify(message, signature)
return verified
}