The envelope extension allows to pass additional data with the request to check user signature and protect from replay attacks. The extension is implemented as cckit middleware.
The envelope is a structure that consists of the following attributes:
- public_key - signer public key (bytes)
- signature - payload signature (bytes)
- nonce - number is given for replay protection (string)
- hash_to_sign - payload hash (bytes)
- hash_func - function used for hashing (string)
- deadline - signature is not valid after deadline, is not mandatory (timestamp)
- channel - chaincode channel
- chaincode - name of chaincode
- method - name of chaincode method
Channel + chaincode + method are used as domain separator to prevent replay attack from other domains (EIP-2612).
- Create the nonce
- Create the message to sign (include message payload and other attributes such as nonce, channel, chaincode, etc.)
- Hash the message (sha-256)
- Sign the hash (ed25519)
- Create the envelope
- Encode the envelope to base64
- Send the envelope in request header (X-Envelop) if the cckit gateway is used
- Receive the envelope from request header
- Decode it from base64 and pack it as a third chaincode argument
- Get the envelope as third argument
- Unmarshall the envelope
- Check envelope attributes (deadline, channel, chaincode, etc)
- Check the nonce
- Recreate the hash from the original message
- Verify the signature using pubkey from the envelope and the hash from the previous step
The sha-256 algorithm is used to hash the message.
The signature is generated using Ed25519.
r := router.New(name).Use(envelope.Verify)
const { sign } = require('tweetnacl');
const { createHash } = require('crypto');
const bs58 = require('bs58');
// CREATE ENVELOPE
function createEnvelope(
payload,
nonce,
channel,
chaincode,
method,
deadline,
keys
) {
const pk = bs58.encode(Buffer.from(keys.publicKey));
// make message to sign
const b1 = Buffer.from(JSON.stringify(payload));
const b2 = Buffer.from(nonce);
const b3 = Buffer.from(channel);
const b4 = Buffer.from(chaincode);
const b5 = Buffer.from(method);
const b6 = Buffer.from(deadline);
const b7 = Buffer.from(pk);
const arr = [b1, b2, b3, b4, b5, b6, b7];
const bb = Buffer.concat(arr);
// hash the message
const hashed = createHash('sha256').update(bb).digest();
// sign the hash
const signature = sign.detached(hashed, keys.secretKey);
// make the envelope
const envelope = {
hash_func: 'SHA256',
hash_to_sign: bs58.encode(hashed),
nonce: nonce,
channel: channel,
method: method,
chaincode: chaincode,
deadline: deadline,
public_key: pk,
signature: bs58.encode(Buffer.from(signature)),
};
console.log(JSON.stringify(envelope));
return JSON.stringify(envelope);
}
// MAIN
const payload = {
symbol: 'GLD',
decimals: '8',
name: 'Gold digital asset',
type: 'DM',
underlying_asset: 'gold',
issuer_id: 'GLDINC',
};
const chaincode = 'envelope-chaincode';
const channel = 'envelope-channel';
const method = 'invokeWithEnvelope';
const nonce = String(new Date().getTime());
const deadline = new Date(new Date().getTime() + 86400000).toISOString(); // if without deadline then use new Date(0).toISOString()
const keys = sign.keyPair();
const envelope = btoa(
createEnvelope(payload, nonce, channel, chaincode, method, deadline, keys)
);