Sign a 32-byte message with the private key, returning a 64-byte signature.
- privateKey
BigInteger
- message
Buffer
signature Buffer
const Buffer = require("safe-buffer").Buffer
const BigInteger = require("bigi")
// signing
const privateKey = BigInteger.fromHex(
"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"
)
const message = Buffer.from(
"243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
"hex"
)
const createdSignature = bitbox.Schnorr.sign(privateKey, message)
console.log("The signature is: " + createdSignature.toString("hex"))
// The signature is: 2a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d1e51a22ccec35599b8f266912281f8365ffc2d035a230434a1a64dc59f7013fd
Verify a 64-byte signature of a 32-byte message against the public key. Throws an Error
if verification fails.
- publicKey.
Buffer
- message.
Buffer
- signature.
Buffer
const Buffer = require("safe-buffer").Buffer
const publicKey = Buffer.from(
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"hex"
)
const message = Buffer.from(
"243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
"hex"
)
const signatureToVerify = Buffer.from(
"2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD",
"hex"
)
try {
bitbox.Schnorr.verify(publicKey, message, signatureToVerify)
console.log("The signature is valid.")
} catch (e) {
console.error("The signature verification failed: " + e)
}
Verify a list of 64-byte signatures as a batch operation. Throws an Error
if verification fails.
- publicKeys.
Buffer[]
- messages.
Buffer[]
- signatures.
Buffer[]
const Buffer = require("safe-buffer").Buffer
const publicKeys = [
Buffer.from(
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"hex"
),
Buffer.from(
"03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B",
"hex"
),
Buffer.from(
"026D7F1D87AB3BBC8BC01F95D9AECE1E659D6E33C880F8EFA65FACF83E698BBBF7",
"hex"
)
]
const messages = [
Buffer.from(
"243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
"hex"
),
Buffer.from(
"5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C",
"hex"
),
Buffer.from(
"B2F0CD8ECB23C1710903F872C31B0FD37E15224AF457722A87C5E0C7F50FFFB3",
"hex"
)
]
const signatures = [
Buffer.from(
"2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD",
"hex"
),
Buffer.from(
"00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380",
"hex"
),
Buffer.from(
"68CA1CC46F291A385E7C255562068357F964532300BEADFFB72DD93668C0C1CAC8D26132EB3200B86D66DE9C661A464C6B2293BB9A9F5B966E53CA736C7E504F",
"hex"
)
]
try {
bitbox.Schnorr.batchVerify(publicKeys, messages, signatures)
console.log("The signatures are valid.")
} catch (e) {
console.error("The signature verification failed: " + e)
}
Aggregates multiple signatures of different private keys over the same message into a single 64-byte signature using a scheme that is safe from rogue-key attacks.
This non-interactive scheme requires the knowledge of all private keys that are participating in the multi-signature creation.
- privateKeys.
BigInteger[]
- message
Buffer
aggregatedSignature Buffer
const Buffer = require("safe-buffer").Buffer
const BigInteger = require("bigi")
const privateKey1 = BigInteger.fromHex(
"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"
)
const privateKey2 = BigInteger.fromHex(
"C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7"
)
const message = Buffer.from(
"243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
"hex"
)
const aggregatedSignature = bitbox.Schnorr.nonInteractive(
[privateKey1, privateKey2],
message
)
// verifying an aggregated signature
const publicKey1 = Buffer.from(
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"hex"
)
const publicKey2 = Buffer.from(
"03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B",
"hex"
)
const X = bitbox.Schnorr.publicKeyCombine([publicKey1, publicKey2])
try {
bitbox.Schnorr.verify(X, message, aggregatedSignature)
console.log("The signature is valid.")
} catch (e) {
console.error("The signature verification failed: " + e)
}
Generate ell
which is the hash over all public keys participating in a session.
- publicKeys.
BigInteger[]
publicKeyHash Buffer
const Buffer = require("safe-buffer").Buffer
const BigInteger = require("bigi")
const publicData = {
pubKeys: [
Buffer.from(
"03846f34fdb2345f4bf932cb4b7d278fb3af24f44224fb52ae551781c3a3cad68a",
"hex"
),
Buffer.from(
"02cd836b1d42c51d80cef695a14502c21d2c3c644bc82f6a7052eb29247cf61f4f",
"hex"
),
Buffer.from(
"03b8c1765111002f09ba35c468fab273798a9058d1f8a4e276f45a1f1481dd0bdb",
"hex"
)
],
message: bitbox.Schnorr.hash(Buffer.from("muSig is awesome!", "utf8")),
pubKeyHash: null,
pubKeyCombined: null,
commitments: [],
nonces: [],
nonceCombined: null,
partialSignatures: [],
signature: null
}
// data only known by the individual party, these values are never shared
// between the signers!
const signerPrivateData = [
// signer 1
{
privateKey: BigInteger.fromHex(
"add2b25e2d356bec3770305391cbc80cab3a40057ad836bcb49ef3eed74a3fee"
),
session: null
},
// signer 2
{
privateKey: BigInteger.fromHex(
"0a1645eef5a10e1f5011269abba9fd85c4f0cc70820d6f102fb7137f2988ad78"
),
session: null
},
// signer 3
{
privateKey: BigInteger.fromHex(
"2031e7fed15c770519707bb092a6337215530e921ccea42030c15d86e8eaf0b8"
),
session: null
}
]
// -----------------------------------------------------------------------
// Step 1: Combine the public keys
// The public keys P_i are combined into the combined public key P.
// This can be done by every signer individually or by the initializing
// party and then be distributed to every participant.
// -----------------------------------------------------------------------
publicData.pubKeyHash = bitbox.Schnorr.computeEll(publicData.pubKeys)
Creates the special rogue-key-resistant combined public key P
by applying the MuSig coefficient to each public key P_i
before adding them together.
- publicKeys.
Buffer[]
- publicKeyHash.
Buffer
X Buffer
// continued from above
publicData.pubKeyCombined = bitbox.Schnorr.publicKeyCombine(
publicData.pubKeys,
publicData.pubKeyHash
)
Creates a signing session. Each participant must create a session and must not share the content of the session apart from the commitment and later the nonce.
- sessionId.
Buffer
- privateKey.
BigInteger
- message.
Buffer
- pubKeyCombined.
Buffer
- ell.
Buffer
- idx.
number
session Session
// continued from above
// -----------------------------------------------------------------------
// Step 2: Create the private signing session
// Each signing party does this in private. The session ID *must* be
// unique for every call to sessionInitialize, otherwise it's trivial for
// an attacker to extract the secret key!
// -----------------------------------------------------------------------
signerPrivateData.forEach((data, idx) => {
const sessionId = bitbox.Crypto.randomBytes(32) // must never be reused between sessions!
data.session = bitbox.Schnorr.sessionInitialize(
sessionId,
data.privateKey,
publicData.message,
publicData.pubKeyCombined,
publicData.pubKeyHash,
idx
)
})
const signerSession = signerPrivateData[0].session
Combines multiple nonces R_i
into the combined nonce R
.
- session.
Session
- nonces.
Buffer[]
nonceCombined Buffer
// continued from above
// -----------------------------------------------------------------------
// Step 3: Exchange commitments (communication round 1)
// The signers now exchange the commitments H(R_i). This is simulated here
// by copying the values from the private data to public data array.
// -----------------------------------------------------------------------
for (let i = 0; i < publicData.pubKeys.length; i++) {
publicData.commitments[i] = signerPrivateData[i].session.commitment
}
// -----------------------------------------------------------------------
// Step 4: Get nonces (communication round 2)
// Now that everybody has commited to the session, the nonces (R_i) can be
// exchanged. Again, this is simulated by copying.
// -----------------------------------------------------------------------
for (let i = 0; i < publicData.pubKeys.length; i++) {
publicData.nonces[i] = signerPrivateData[i].session.nonce
}
// -----------------------------------------------------------------------
// Step 5: Combine nonces
// The nonces can now be combined into R. Each participant should do this
// and keep track of whether the nonce was negated or not. This is needed
// for the later steps.
// -----------------------------------------------------------------------
publicData.nonceCombined = bitbox.Schnorr.sessionNonceCombine(
signerSession,
publicData.nonces
)
signerPrivateData.forEach(
data => (data.session.nonceIsNegated = signerSession.nonceIsNegated)
)
Creates a partial signature s_i
for a participant.
- session.
Session
- message.
Buffer
- nonceCombined.
Buffer
- publicKeyCombined.
Buffer
partialSignature BigInteger
// continued from above
// -----------------------------------------------------------------------
// Step 6: Generate partial signatures
// Every participant can now create their partial signature s_i over the
// given message.
// -----------------------------------------------------------------------
signerPrivateData.forEach(data => {
data.session.partialSignature = bitbox.Schnorr.partialSign(
data.session,
publicData.message,
publicData.nonceCombined,
publicData.pubKeyCombined
)
})
Verifies a partial signature s_i
against the participant's public key P_i
. Throws an Error
if verification fails.
- session.
Session
- partialSig.
BigInteger
- nonceCombined.
Buffer
- idx.
number
- publicKey.
Buffer
- nonce.
Buffer
// continued from above
// -----------------------------------------------------------------------
// Step 7: Exchange partial signatures (communication round 3)
// The partial signature of each signer is exchanged with the other
// participants. Simulated here by copying.
// -----------------------------------------------------------------------
for (let i = 0; i < publicData.pubKeys.length; i++) {
publicData.partialSignatures[i] =
signerPrivateData[i].session.partialSignature
}
// -----------------------------------------------------------------------
// Step 8: Verify individual partial signatures
// Every participant should verify the partial signatures received by the
// other participants.
// -----------------------------------------------------------------------
for (let i = 0; i < publicData.pubKeys.length; i++) {
bitbox.Schnorr.partialSignatureVerify(
signerSession,
publicData.partialSignatures[i],
publicData.nonceCombined,
i,
publicData.pubKeys[i],
publicData.nonces[i]
)
}
Combines multiple partial signatures into a Schnorr signature (s, R)
that can be verified against the combined public key P
.
- nonceCombined.
Buffer
- partialSignatures.
BigInteger[]
signature Buffer
// continued from above
// -----------------------------------------------------------------------
// Step 9: Combine partial signatures
// Finally, the partial signatures can be combined into the full signature
// (s, R) that can be verified against combined public key P.
// -----------------------------------------------------------------------
publicData.signature = bitbox.Schnorr.partialSignaturesCombine(
publicData.nonceCombined,
publicData.partialSignatures
)
// -----------------------------------------------------------------------
// Step 10: Verify signature
// The resulting signature can now be verified as a normal Schnorr
// signature (s, R) over the message m and public key P.
// -----------------------------------------------------------------------
bitbox.Schnorr.verify(
publicData.pubKeyCombined,
publicData.message,
publicData.signature
)